159 lines
5.5 KiB
JavaScript
159 lines
5.5 KiB
JavaScript
|
// number of messages to load at once when clicking "load more"
|
||
|
const MESSAGE_LOAD_LIMIT = 150;
|
||
|
const BASE_TITLE = document.title;
|
||
|
|
||
|
let currentChannel;
|
||
|
//image file formats which can be rendered in browser
|
||
|
let imageFormats = [
|
||
|
"bmp",
|
||
|
"gif",
|
||
|
"ico",
|
||
|
"jpeg",
|
||
|
"jpe",
|
||
|
"jpg",
|
||
|
"jfif",
|
||
|
"apng",
|
||
|
"png",
|
||
|
"tga",
|
||
|
"tiff",
|
||
|
"tif",
|
||
|
"svg",
|
||
|
"webp",
|
||
|
];
|
||
|
|
||
|
const loadMessages = async (channelId, skip = 0, initial = false) => {
|
||
|
let channelEl = document.getElementById(`channel-${channelId}`);
|
||
|
// hack to avoid loading the same messages repeatedly if clicking the channel repeatedly without switching
|
||
|
if (channelEl.classList.contains("channel-active") && skip == 0) return;
|
||
|
Array.from(document.querySelectorAll(".channel-active")).forEach(ch => {
|
||
|
ch.classList.remove("channel-active");
|
||
|
});
|
||
|
channelEl.classList.add("channel-active");
|
||
|
window.location.hash = channelId;
|
||
|
|
||
|
document.title = `${channelEl.children[0].textContent} | ${BASE_TITLE}`;
|
||
|
|
||
|
const messages = document.getElementById("messages");
|
||
|
const prevHeight = Array.from(messages.children).reduce((accumulator, element) => accumulator + element.offsetHeight, 0);
|
||
|
const loadButton = document.getElementById("load-more-button");
|
||
|
if (loadButton) {
|
||
|
loadButton.outerHTML = "";
|
||
|
}
|
||
|
if (currentChannel !== channelId) messages.innerHTML = "";
|
||
|
currentChannel = channelId;
|
||
|
const channelData = await fetch(
|
||
|
`/api/v1/discord/channel/${channelId}?o=${skip}`
|
||
|
).then(resp => resp.json());
|
||
|
channelData.map((msg) => {
|
||
|
let dls = "";
|
||
|
let avatarurl = "";
|
||
|
let embeds = "";
|
||
|
if (msg.content) {
|
||
|
msg.content = msg.content
|
||
|
.replace(/&/g, "&")
|
||
|
.replace(/</g, "<")
|
||
|
.replace(/"/g, """)
|
||
|
.replace(/<br \/>/g, "");
|
||
|
|
||
|
// XXX scanning for < here because < was encoded—not really ideal
|
||
|
// the a? is because animated emojis are <a:[emoji name]:[emoji id]>
|
||
|
msg.content = msg.content.replace(/<a?:(.+?):(\d+)>/g, '<img class="emoji" title=":$1:" src="https://cdn.discordapp.com/emojis/$2">');
|
||
|
}
|
||
|
msg.attachments.map((dl) => {
|
||
|
if (imageFormats.includes(dl.name.split(".").pop())) {
|
||
|
dls += `<a href="/data/${dl.path}?f=${dl.name}" target="_blank"><img class="user-post-image" style="max-width:300px" src="/thumbnail/data${dl.path}" data-tries="2" onerror="if(this.dataset['tries']!='0'){this.src='/data/${dl.path}?f=${dl.name}';this.dataset['tries']--;}"></a><br>`;
|
||
|
} else {
|
||
|
dls += `<a href="/data/${dl.path}?f=${dl.name}">Download ${dl.name
|
||
|
.replace(/&/g, "&")
|
||
|
.replace(/</g, "<")
|
||
|
.replace(/"/g, """)}</a><br>`;
|
||
|
}
|
||
|
});
|
||
|
msg.embeds.map((embed) => {
|
||
|
// XXX this is almost definitely wrong
|
||
|
embeds += `
|
||
|
<a href="/data/${embed.url}" target="_blank">
|
||
|
<div class="embed-view" style="max-width:300px">
|
||
|
<p>${(embed.description || embed.title || "")
|
||
|
.replace(/&/g, "&")
|
||
|
.replace(/</g, "<")
|
||
|
.replace(/"/g, """)}</p>
|
||
|
</div>
|
||
|
</a>
|
||
|
`;
|
||
|
});
|
||
|
if (msg.author.avatar) {
|
||
|
avatarurl = `https://cdn.discordapp.com/avatars/${msg.author.id}/${msg.author.avatar}`;
|
||
|
} else {
|
||
|
avatarurl = "/static/discordgrey.png";
|
||
|
}
|
||
|
|
||
|
messages.innerHTML = `
|
||
|
<div class="message">
|
||
|
<div class="avatar">
|
||
|
<img class="avatar" src="${avatarurl}" onerror="this.src = '/static/discordgrey.png'" referrerpolicy="no-referrer" alt="">
|
||
|
</div>
|
||
|
<div style="display:inline-block" id="${msg.id}">
|
||
|
<div class="message-header">
|
||
|
<b><p>${msg.author.username.replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """)}</p></b>
|
||
|
<p style="color:#757575">${new Date(msg.published)}</p>
|
||
|
</div>
|
||
|
<p><pre class="message__body">${msg.content}</pre></p>
|
||
|
${dls}
|
||
|
${embeds}
|
||
|
</div>
|
||
|
</div>
|
||
|
` + messages.innerHTML;
|
||
|
});
|
||
|
|
||
|
// avoid adding the button if there are no more messages
|
||
|
if (channelData.length == MESSAGE_LOAD_LIMIT) {
|
||
|
messages.innerHTML = `
|
||
|
<div class="message" id="load-more-button">
|
||
|
<button onClick="loadMessages('${channelId}', ${skip + MESSAGE_LOAD_LIMIT})" class="load-more-button">
|
||
|
Load More
|
||
|
</button>
|
||
|
</div>
|
||
|
` + messages.innerHTML;
|
||
|
} else {
|
||
|
messages.innerHTML = `
|
||
|
<div class="message" id="load-more-button">
|
||
|
<p>End of channel history</p>
|
||
|
</div>
|
||
|
` + messages.innerHTML;
|
||
|
}
|
||
|
|
||
|
messages.scrollTop = messages.scrollHeight - prevHeight;
|
||
|
};
|
||
|
|
||
|
const load = async () => {
|
||
|
const pathname = window.location.pathname.split("/");
|
||
|
const serverData = await fetch(`/api/v1/discord/channel/lookup/${pathname[3]}`);
|
||
|
const server = await serverData.json();
|
||
|
const channels = document.getElementById("channels");
|
||
|
server.forEach((ch) => {
|
||
|
const channel = document.getElementById(`channel-${ch.id}`);
|
||
|
if (!channel) {
|
||
|
channels.innerHTML += `
|
||
|
<div class="channel" id="channel-${ch.id}" onClick="loadMessages('${ch.id}')">
|
||
|
<p>#${ch.name.replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """)}</p>
|
||
|
</div>
|
||
|
`;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const serverID = window.location.href.match(/\/discord\/server\/(\d+)/)[1];
|
||
|
|
||
|
channels.innerHTML += `
|
||
|
<div class="channel" id="linked-accounts" style="position: fixed; bottom: 0;">
|
||
|
<a href="/discord/user/${serverID}/links">Linked Accounts</a>
|
||
|
</div>
|
||
|
`;
|
||
|
|
||
|
if (window.location.hash !== "") {
|
||
|
loadMessages(window.location.hash.slice(1).split("-")[0]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
load();
|