kemono2/client/static/js/discord.js
2024-07-04 22:08:17 +02:00

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, "&lt;")
.replace(/"/g, "&quot;")
.replace(/<br \/>/g, "");
// XXX scanning for &lt; here because < was encoded—not really ideal
// the a? is because animated emojis are <a:[emoji name]:[emoji id]>
msg.content = msg.content.replace(/&lt;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, "&amp;")
.replace(/</g, "&lt;")
.replace(/"/g, "&quot;")}</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, "&amp;")
.replace(/</g, "&lt;")
.replace(/"/g, "&quot;")}</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, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;")}</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, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;")}</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();