PYRSS-Website/apps/static/js/home/subscriptions.js

631 lines
22 KiB
JavaScript

var subTable = null;
// Create subscription table
function initSubscriptionTable() {
subTable = $("#subTable").DataTable({
info: false,
paging: false,
searching: false,
autoWidth: false,
order: [],
select: {
style: "multi+shift",
selector: 'th:first-child input[type="checkbox"]'
},
columnDefs: [
{ orderable: false, targets: "no-sort" },
{
targets: 0,
checkboxes: { selectRow: true }
},
],
columns: [
{
// Select row checkbox column
title: '<input type="checkbox" class="form-check-input table-select-all" />',
data: null,
orderable: false,
className: "text-center col-switch-width",
render: function() {
return '<input type="checkbox" class="form-check-input table-select-row" />'
}
},
{ title: "ID", data: "id", visible: false },
{
title: "Name",
data: "name",
className: "text-truncate",
render: function(data, type, row) {
return `<a href="#" onclick="showEditSubModal(${row.id})" class="text-decoration-none">${data}</a>`;
}
},
{
title: "URL",
data: "url",
className: "text-truncate",
render: function(data, type) {
return `<a href="${data}" class="text-decoration-none" target="_blank">${data}</a>`;
}
},
{
title: "Channels",
data: "channels_count",
render: function(data) {
return `<span class="badge text-bg-secondary">${data}</span>`;
}
},
// {
// title: "Content",
// data: "channels_count",
// render: function(data, type) {
// }
// },
{
title: "Created",
data: "creation_datetime",
render: function(data, type) {
// return new Date(data).toISOString().split("T")[0];
let dateTime = new Date(data);
let dateTimeString = formatDate(dateTime);
return $(`
<span data-bs-trigger="hover focus"
data-bs-toggle="popover"
data-bs-content="${dateTimeString}">
${dateTime.toISOString().split("T")[0]}
</span>
`).popover()[0];
}
},
{
title: "Notes",
data: "extra_notes",
orderable: false,
className: "text-center",
render: function(data, type) {
if (!data) return "";
return $(`
<i class="bi bi-chat-left-text"
data-bs-trigger="hover focus"
data-bs-toggle="popover"
data-bs-title="Extra Notes"
data-bs-content="${data}">
</i>
`).popover()[0];
}
},
{
title: "Active",
data: "active",
orderable: false,
className: "text-center form-switch",
render: function(data, type) {
return `<input type="checkbox" class="sub-toggle-active form-check-input ms-0" ${data ? "checked" : ""} />`
}
},
{
orderable: false,
className: "p-0",
render: function(data, type, row) {
return `<div class="h-100" style="background-color: #${row.embed_colour}; width: .25rem;">&nbsp;</div>`
}
}
]
});
// subTable.on('page.dt length.dt', async function() {
// await loadSubscriptions(getCurrentlyActiveServer().guild_id);
// });
}
$("#subTable").on("change", ".sub-toggle-active", async function () {
/*
Lock all toggles to soft-prevent spam.
There is a rate limit, but allowing the user to
reach it from this toggle would be bad.
*/
$(".sub-toggle-active").prop("disabled", true);
try {
const active = $(this).prop("checked");
const sub = subTable.row($(this).closest("tr")).data();
// Update the table row
sub.active = active;
subTable.data(sub).draw();
// Update the database
const subPrimaryKey = await saveSubscription(
sub.id,
sub.name,
sub.url,
sub.guild_id,
sub.extra_notes,
sub.filters,
{
title: sub.article_title_mutators.map(mutator => mutator.id),
desc: sub.article_desc_mutators.map(mutator => mutator.id)
},
sub.embed_colour,
sub.article_fetch_image,
sub.published_threshold,
active,
handleErrorMsg=false
);
if (!subPrimaryKey)
throw Error("This subscription no longer exists.");
showToast(
active ? "success" : "danger",
"Subscription " + (active ? "Activated" : "Deactivated"),
"Subscription ID: " + subPrimaryKey
);
}
catch (error) {
console.error(error);
showToast(
"danger",
"Error Updating Subscription",
`Tried to toggle activeness, but encountered a problem. <br><code>${error}</code>`
);
}
finally {
// Re-enable toggles after 500ms
setTimeout(() => { $(".sub-toggle-active").prop("disabled", false); }, 500)
}
});
// Open new subscription modal
$("#addSubscriptionBtn").on("click", async function() {
await showEditSubModal(-1);
});
async function showEditSubModal(subId) {
if (subId === -1) {
$("#subFormModal .form-create, #subAdvancedModal .form-create").show();
$("#subFormModal .form-edit, #subAdvancedModal .form-edit").hide();
$("#subFormModal input, #subFormModal textarea").val("");
$("#subChannels").val("").change();
$("#subFilters").val("").change();
$("#subTitleMutators").val("").change();
$("#subDescMutators").val("").change();
$("#subActive").prop("checked", true);
// $("#subImagePreview img").attr("src", "").hide();
// $("#subImagePreview small").show();
$("#subResetEmbedColour").click();
$("#subArticleFetchImage").prop("checked", true);
}
else {
$("#subFormModal .form-create, #subAdvancedModal .form-create").hide();
$("#subFormModal .form-edit, #subAdvancedModal .form-edit").show();
const subscription = subTable.row(function(idx, data, node) {
return data.id === subId;
}).data();
$("#subName").val(subscription.name);
$("#subUrl").val(subscription.url);
$("#subExtraNotes").val(subscription.extra_notes);
$("#subActive").prop("checked", subscription.active);
$("#subTitleMutators").val("").change();
$("#subTitleMutators").val(subscription.article_title_mutators.map(mutator => mutator.id)).change();
$("#subDescMutators").val("").change();
$("#subDescMutators").val(subscription.article_desc_mutators.map(mutator => mutator.id)).change();
const channels = await getSubChannels(subscription.id);
$("#subChannels").val("").change();
$("#subChannels").val(channels.results.map(channel => channel.channel_id)).change();
$("#subFilters").val("").change();
$("#subFilters").val(subscription.filters).change();
subSetHexColour(`#${subscription.embed_colour}`);
$("#subArticleFetchImage").prop("checked", subscription.article_fetch_image);
$("#subPubThreshold").parent().datepicker("setDate", subscription.published_threshold);
}
$("#subId").val(subId);
$("#subFormModal").modal("show");
}
$("#subForm").on("submit", async function(event) {
event.preventDefault();
var id = $("#subId").val();
name = $("#subName").val();
url = $("#subUrl").val();
guildId = getCurrentlyActiveServer().guild_id;
extraNotes = $("#subExtraNotes").val();
subChannels = $("#subChannels option:selected").toArray().map(channel => channel.value);
subFilters = $("#subFilters option:selected").toArray().map(filter => parseInt(filter.value));
subMutators = {
title: $("#subTitleMutators option:selected").toArray().map(mutator => parseInt(mutator.value)),
desc: $("#subDescMutators option:selected").toArray().map(mutator => parseInt(mutator.value))
}
subEmbedColour = $("#subEmbedColour").val().split("#")[1];
articleFetchImage = $("#subArticleFetchImage").prop("checked")
publishedThreshold = $("#subPubThreshold").val();
active = $("#subActive").prop("checked");
var subPrimaryKey = await saveSubscription(id, name, url, guildId, extraNotes, subFilters, subMutators, subEmbedColour, articleFetchImage, publishedThreshold, active);
if (!subPrimaryKey) {
alert("prevented /subscriptions/false/subchannels");
return
}
await deleteSubChannels(subPrimaryKey);
subChannels.forEach(async channelId => {
await saveSubChannel(channelId, subPrimaryKey);
});
if (subPrimaryKey)
showToast("success", "Subscription Saved", "Subscription ID: " + subPrimaryKey);
await loadSubscriptions(guildId);
$("#subFormModal").modal("hide");
});
async function saveSubscription(id, name, url, guildId, extraNotes, filters, mutators, embedColour, articleFetchImage, publishedTheshold, active, handleErrorMsg=true) {
var formData = new FormData();
formData.append("name", name);
formData.append("url", url);
formData.append("guild_id", guildId);
formData.append("extra_notes", extraNotes);
filters.forEach(filter => formData.append("filters", filter));
mutators.title.forEach(mutator => formData.append("article_title_mutators", mutator));
mutators.desc.forEach(mutator => formData.append("article_desc_mutators", mutator));
formData.append("embed_colour", embedColour);
formData.append("article_fetch_image", articleFetchImage);
formData.append("published_threshold", publishedTheshold);
formData.append("active", active);
var response;
try {
if (id === "-1") response = await newSubscription(formData);
else response = await editSubscription(id, formData);
}
catch (err) {
console.error(err)
if (handleErrorMsg)
showToast("danger", "Subscription Error", err.responseText, 18000);
return false;
}
return response.id;
}
async function saveSubChannel(channelId, subscriptionId) {
var formData = new FormData();
formData.append("channel_id", channelId);
formData.append("subscription", subscriptionId);
var response
try {
response = await newSubChannel(formData);
}
catch (error) {
console.log(error);
showToast("danger", "Failed to save subchannel", error, 18000);
return false
}
return response.id
}
function clearExistingSubRows() {
$("#subTable thead .table-select-all").prop("checked", false).prop("indeterminate", false);
subTable.clear().draw(false);
}
$("#refreshSubscriptionBtn").on("click", async function() {
loadSubscriptions(getCurrentlyActiveServer().guild_id);
});
async function loadSubscriptions(guildId, page=1, pageSize=null) {
if (!guildId)
return;
if (!pageSize)
pageSize = $("#subTablePageSize").val();
$("#deleteSelectedSubscriptionsBtn").prop("disabled", true);
clearExistingSubRows();
try {
const subscriptions = await getSubscriptions(guildId, page, pageSize);
subTable.rows.add(subscriptions.results).draw(false);
updateTablePagination("#subPagination", page, pageSize, subscriptions.count, subscriptions.next, subscriptions.previous);
updateTablePaginationInfo("#subTablePageInfo", subscriptions.results.length, subscriptions.count);
$("#subTable thead .table-select-all").prop("disabled", subscriptions.results.length === 0);
console.debug("loading subs, " + subscriptions.results.length + " found");
}
catch (err) {
console.error(err)
console.error(JSON.stringify(err, null, 4));
showToast("danger", `Error Loading Subscriptions: HTTP ${err.status}`, err.responseJSON.message, 15000);
}
}
// #region Server Change Event Handler
$(document).on("selectedServerChange", async function() {
// Hide alerts
$("#serverJoinAlert").attr("style", "display: none !important");
const activeServer = getCurrentlyActiveServer();
await loadSubscriptions(activeServer.guild_id);
await loadChannelOptions(activeServer.guild_id);
await loadFilterOptions(activeServer.guild_id);
await loadMutatorOptions();
})
// #endregion
// Dev button (to be removed)
// auto fills dummy data into form fields to quickly create test subs
$("#devGenerateSub").on("click", function() {
const items = [
["BBC News · Top Stories", "http://feeds.bbci.co.uk/news/rss.xml"],
["BBC News · World", "http://feeds.bbci.co.uk/news/world/rss.xml"],
["BBC News · Entertainment", "http://feeds.bbci.co.uk/news/entertainment_and_arts/rss.xml"],
["BBC News · UK", "http://feeds.bbci.co.uk/news/uk/rss.xml"],
["BBC News · Business", "http://feeds.bbci.co.uk/news/business/rss.xml"],
["BBC News · Politics", "http://feeds.bbci.co.uk/news/politics/rss.xml"],
["BBC News · Health", "http://feeds.bbci.co.uk/news/health/rss.xml"],
["BBC News · Science", "http://feeds.bbci.co.uk/news/science_and_environment/rss.xml"],
["BBC News · Technology", "http://feeds.bbci.co.uk/news/technology/rss.xml"],
["BBC News · Education", "http://feeds.bbci.co.uk/news/education/rss.xml"],
["BBC News · England", "http://feeds.bbci.co.uk/news/england/rss.xml"],
["BBC News · Scotland", "http://feeds.bbci.co.uk/news/scotland/rss.xml"],
["BBC News · Wales", "http://feeds.bbci.co.uk/news/wales/rss.xml"],
["BBC News · Northern Ireland", "http://feeds.bbci.co.uk/news/northern_ireland/rss.xml"],
["BBC News · Europe", "http://feeds.bbci.co.uk/news/europe/rss.xml"],
["BBC News · Latin America", "http://feeds.bbci.co.uk/news/latin_america/rss.xml"],
["BBC News · US & Canada", "http://feeds.bbci.co.uk/news/us_and_canada/rss.xml"],
["BBC News · Middle East", "http://feeds.bbci.co.uk/news/middle_east/rss.xml"],
["BBC News · Asia", "http://feeds.bbci.co.uk/news/asia/rss.xml"],
["BBC News · Africa", "http://feeds.bbci.co.uk/news/africa/rss.xml"],
["BBC Sports", "https://news.bbc.co.uk/sport1/hi/help/rss/default.stm"],
["This Week in Self-Hosted", "https://selfh.st/rss/"],
["The Babylon Bee", "https://babylonbee.com/feed"],
["Reddit · r/memes", "https://reddit.com/r/memes/.rss"],
["Reddit · r/selfhosted", "https://reddit.com/r/selfhosted/.rss"],
["Sky News", "http://feeds.skynews.com/feeds/rss/home.xml"],
["Sky News · UK", "http://feeds.skynews.com/feeds/rss/uk.xml"],
["Sky News · Entertainment", "http://feeds.skynews.com/feeds/rss/entertainment.xml"],
["Sky News · Strange News", "http://feeds.skynews.com/feeds/rss/strange.xml"]
];
const selected = items[Math.floor(Math.random()*items.length)]
$("#subName").val(selected[0]);
$("#subUrl").val(selected[1]);
showToast("info", "Generated", "Picked random sub data");
});
// #region Delete Subscription Buttons
// Delete button on the 'edit subscription' modal
$("#deleteEditSub").on("click", async function() {
const subId = $("#subId").val();
await deleteSubscription(subId);
await loadSubscriptions(getCurrentlyActiveServer().guild_id);
$("#subFormModal").modal("hide");
showToast("danger", "Deleted Subscription", "Subscription ID: " + subId);
});
$("#deleteSelectedSubscriptionsBtn").on("click", async function() {
// showToast("danger", "Not Implemented", "This feature isn't implemented");
var rows = subTable.rows(".selected").data();
$.each(rows, async function() {
// alert(JSON.stringify(this, null, 4));
await deleteSubscription(this.id);
showToast("danger", "Deleted Subscription", "Subscription ID: " + this.id);
});
await loadSubscriptions(getCurrentlyActiveServer().guild_id);
})
// #endregion
// #region Load Modal Options
async function loadChannelOptions(guildId) {
// Disable input while options are loading
$("#subChannels").prop("disabled", true);
// Delete existing options
$("#subChannels option").each(function() {
if ($(this).val())
$(this).remove();
});
// Clear select2 input
$("#subChannels").val("").change();
try {
const channels = await loadChannels(guildId);
console.debug(JSON.stringify(channels));
// If we have reached the discord API rate limit
if (channels.message && channels.message.includes("rate limit")) {
throw new Error(
`${channels.message} Retry after ${channels.retry_after} seconds.`
)
}
// If we can't fetch channels due to error
if (channels.code === 50001) {
// Also check that the user hasn't changed the currently active guild, otherwise
// the alert will show under the wrong server.
if (getCurrentlyActiveServer().guild_id === guildId)
showServerJoinAlert();
const guildName = getServerFromSnowflake(guildId).name;
throw new Error(
`Unable to retrieve channels from Guild <b>${guildName}</b>.
Ensure that @PYRSS is a member with permissions
to view channels.`
);
}
channels.forEach(channel => {
// We only want TextChannels, which have a type of 0
if (channel.type !== 0)
return;
$("#subChannels").append($("<option>", {
text: "#" + channel.name,
value: channel.id
}));
});
}
catch(error) {
console.error(error);
showToast("danger", "Error loading channels", error, 18000);
}
finally {
// Re-enable the input
$("#subChannels").prop("disabled", false);
}
}
async function loadMutatorOptions() {
// Disable input while options are loading
$(".sub-mutators-field").prop("disabled", true);
// Delete existing options
$(".sub-mutators-field option").each(function() {
if ($(this).val())
$(this).remove();
});
// Clear select2 input
$(".sub-mutators-field").val("").change();
try {
const mutators = await getMutators();
console.log(JSON.stringify(mutators));
mutators.forEach(mutator => {
$(".sub-mutators-field").append($("<option>", {
text: mutator.name,
value: mutator.id
}));
});
}
catch(error) {
console.error(error);
showToast("danger", "Error loading sub mutators", error, 18000);
}
finally {
// Re-enable the input
$(".sub-mutators-field").prop("disabled", false);
}
}
async function loadFilterOptions(guildId) {
// Disable input while options are loading
$("#subFilters").prop("disabled", true);
// Delete existing options
$("#subFilters option").each(function() {
if ($(this).val())
$(this).remove();
});
// Clear select2 input
$("#subFilters").val("").change();
try {
const filters = await getFilters(guildId);
console.log(JSON.stringify(filters));
filters.results.forEach(filter => {
$("#subFilters").append($("<option>", {
text: filter.name,
value: filter.id
}));
});
}
catch(error) {
console.error(error);
showToast("danger", "Error loading sub filters", error, 18000);
}
finally {
// Re-enable the input
$("#subFilters").prop("disabled", false);
}
}
// #endregion
// #region Bot Not in Server Alert
function showServerJoinAlert() {
const guildId = getCurrentlyActiveServer().guild_id;
const inviteUrl = `https://discord.com/oauth2/authorize
?client_id=1129345991758336020
&permissions=2147534848&scope=bot+applications.commands
&guild_id=${guildId}
&disable_guild_select=true`
$("#serverJoinAlert a.alert-link").attr("href", inviteUrl);
$("#serverJoinAlert").show();
}
// #endregion
// #region Colour Controls
$("#subEmbedColour").on("change", function() {
$("#subEmbedColourText").val($(this).val());
});
$("#subEmbedColourText").on("change", function() {
$("#subEmbedColour").val($(this).val());
});
function subSetHexColour(hexString) {
$("#subEmbedColour").val(hexString);
$("#subEmbedColourText").val(hexString);
}
$(document).ready(() => {$("#subResetEmbedColour").click();});
$("#subResetEmbedColour").on("click", function() {
subSetHexColour("#3498db");
});
$("#subRandomEmbedColour").on("click", function() {
subSetHexColour(`#${genHexString(6)}`);
});
// #endregion