diff --git a/apps/home/static/home/js/content.js b/apps/home/static/home/js/content.js deleted file mode 100644 index fc82637..0000000 --- a/apps/home/static/home/js/content.js +++ /dev/null @@ -1,254 +0,0 @@ -var contentTable; - contentOptions = null; - channelResolveInterval = null; - -async function initContentTable() { - contentOptions = await getTrackedContentOptions(); - await initTable("#contentTabPane", "contentTable", loadContent, null, deleteSelectedContent, contentOptions); - - contentTable = $("#contentTable").DataTable({ - info: false, - paging: false, - ordering: 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: '', - data: null, - orderable: false, - className: "text-center col-switch-width", - render: function() { - return '' - } - }, - { data: "id", visible: false }, - { - title: "GUID", - data: "guid", - className: "text-truncate mw-10rem", - }, - { - title: "Name", - data: "title", - className: "text-truncate", - render: function(data, type, row) { - const title = sanitise(data); - const url = sanitise(row.url); - return `${title}` - } - }, - { - title: "Subscription", - data: "subscription.name", - className: "text-nowrap", - render: function(data, type, row) { - const subName = sanitise(data); - return `` - } - }, - { - title: "Blocked", - data: "blocked", - className: "text-center col-1", - render: function(data) { - return data ? `` : "" - } - }, - { - title: "Channel", - data: "channel_id", - className: "text-start", - render: function(data, type, row) { - const channelId = sanitise(data); - const messageId = sanitise(row.message_id); - return `
-
- Loading... -
-
`; - } - }, - { - title: "Created", - data: "creation_datetime", - className: "text-nowrap", - render: function(data, type) { - let dateTime = new Date(data); - return $(` - %H:%M:%S")}"> - ${formatStringDate(dateTime, "%D, %b %Y")} - - `).popover()[0]; - } - }, - { - orderable: false, - className: "p-0", - render: function(data, type, row) { - const embedColour = sanitise(row.subscription.embed_colour); - return `
 
` - } - } - ] - }); - - bindTableCheckboxes("#contentTable", contentTable, "#contentTabPane .table-del-btn"); - - contentTable.on("draw", function() { - restartResolveChannelNamesTask(); - }); -} - - -// #region Resolve Channels - -function restartResolveChannelNamesTask() { - clearInterval(channelResolveInterval); - startResolveChannelNamesTask(); -} - -function startResolveChannelNamesTask() { - const guildId = selectedServer.id; - channelResolveInterval = setInterval(function() { - if (resolveChannelNames(guildId)) - clearInterval(channelResolveInterval); - }, 50) -} - -function resolveChannelNames(guildId) { - if (!discordChannels.length) { - return false - } - - $(".resolve-channel-name").each(function() { - const channelId = $(this).data("channel-id"); - const messageId = $(this).data("msg-id"); - console.log(channelId + " " + messageId); - const channel = discordChannels.find(channel => channel.value === channelId); - - if (channel) { - const href = `https://discord.com/channels/${guildId}/${channelId}/${messageId}/`; - $(this).replaceWith( - $("").text(channel.text) - .attr("href", href) - .attr("target", "_blank") - .addClass("btn btn-link text-start text-decoration-none text-nowrap") - ); - } - }); - - return true; -} - -// #endregion - -async function goToSubscription(subId) { - $("#subscriptionsTab").click(); - await showEditSubModal(subId); -} - - -// #region Delete Content - -async function deleteSelectedContent() { - const rows = contentTable.rows(".selected").data().toArray(); - const names = rows.map(row => { return row.title }); - const namesString = arrayToHtmlList(names, true).prop("outerHTML"); - const isMany = names.length > 1; - - await confirmationModal( - `Delete ${isMany ? "Many Tracked Contents" : "a Tracked Content"}`, - `Do you wish to permanently delete ${isMany ? "these" : "this"} ${names.length} Tracked Content${isMany ? "s" : ""}?

${namesString}`, - "danger", - async () => { - rows.forEach(async row => { await deleteTrackedContent(row.id) }); - - showToast( - "danger", - `Deleted ${names.length} Content${isMany ? "s" : ""}`, - `${arrayToHtmlList(names, false).prop("outerHTML")}`, - 12000 - ); - - // Multi-deletion can take time, this timeout ensures the refresh is accurate - setTimeout(async () => { - await loadContent(selectedServer.id); - }, 600); - }, - null - ); -} - -// #endregion - - -function clearExistingContentRows() { - $("#contentTable thead .table-select-all").prop("checked", false).prop("indeterminate", false); - contentTable.clear().draw(false); -} - -$("#contentTabPane").on("click", ".table-refresh-btn", async function() { - await loadContent(selectedServer.id); -}); - - -// #region Load Content - -async function loadContent(guildId) { - - if (!guildId) - return; - - setTableFilter("contentTable", "subscription__guild_id", guildId); - ensureTablePagination("contentTable"); - - $("#contentTabPane .table-del-btn").prop("disabled", true); - clearExistingContentRows(); - - try { - var content = await getTrackedContent(tableFilters["contentTable"], tableSorts["contentTable"]); - contentTable.rows.add(content.results).draw(false); - } - catch (err) { - console.error(err); - showToast("danger", `Error loading Tracked Content: HTTP ${err.status}`, err, 15000); - return; - } - - updateTableContainer( - "contentTabPane", - tableFilters["contentTable"]["page"], - tableFilters["contentTable"]["page_size"], - content.results.length, - content.count, - content.next, - content.previous - ); - - $("#contentTable thead .table-select-all").prop("disabled", content.results.length === 0); - console.debug(`loaded filters, ${content.results.length} found`) -} - -$(document).on("selectedServerChange", async function() { - await loadContent(selectedServer.id); -}); - -// #endregion diff --git a/apps/home/static/home/js/filters.js b/apps/home/static/home/js/filters.js deleted file mode 100644 index 7496c7c..0000000 --- a/apps/home/static/home/js/filters.js +++ /dev/null @@ -1,310 +0,0 @@ -var filtersTable; - filterOptions = null; - -// Create filters table -async function initFiltersTable() { - filterOptions = await getFilterOptions(); - await initTable("#filtersTabPane", "filtersTable", loadFilters, showEditFilterModal, deleteSelectedFilters, filterOptions); - - filtersTable = $("#filtersTable").DataTable({ - info: false, - paging: false, - ordering: 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: '', - data: null, - orderable: false, - className: "text-center col-switch-width", - render: function() { - return '' - } - }, - { title: "ID", data: "id", visible: false }, - { - title: "Name", - data: "name", - render: function(data, type, row) { - const name = sanitise(data); - return `` - } - }, - { - title: "Matching Algorithm", - data: "matching_algorithm", - render: function(data) { - switch (data) { - case 1: return "Any Word"; - case 2: return "All Words"; - case 3: return "Exact Match"; - case 4: return "Regular Expression"; - case 5: return "Fuzzy Match"; - default: - console.error(`unknown matching algorithm '${data}'`); - return sanitise(data); - } - } - }, - { - title: "Match", - data: "match" - }, - { - title: "Case Sensitivity", - data: "is_insensitive", - render: function(data) { - return data ? "Insensitive" : "Sensitive" - } - }, - { - title: "Control List", - data: "is_whitelist", - render: function(data) { - return data ? "Whitelist" : "Blacklist"; - } - } - ] - }); - - bindTableCheckboxes("#filtersTable", filtersTable, "#filtersTabPane .table-del-btn"); -} - -$("#addFilterBtn").on("click", async function() { - await showEditFilterModal(-1); -}) - -async function showEditFilterModal(filterId) { - - if (filterId === -1) { - $("#filterFormModal input, #filterFormModal textarea").val(""); - $("#filterFormModal input:checkbox").prop("checked", false); - - $("#filterAlgorithm").val("").change(); - - $("#filterFormModal .form-create").show(); - $("#filterFormModal .form-edit").hide(); - } - else { - const filter = filtersTable.row(function(idx, data, node) { - return data.id === filterId; - }).data(); - - $("#filterAlgorithm").val("").change(); - $("#filterAlgorithm").val(filter.matching_algorithm).change(); - - $("#filterName").val(filter.name); - $("#filterMatch").val(filter.match); - $("#filterWhitelist").prop("checked", filter.is_whitelist); - $("#filterInsensitive").prop("checked", filter.is_insensitive); - - $("#filterFormModal .form-create").hide(); - $("#filterFormModal .form-edit").show(); - } - - $("#filterId").val(filterId); - $("#filterFormModal").modal("show"); -} - -$("#filterForm").on("submit", async function(event) { - event.preventDefault(); - - var id = $("#filterId").val(); - name = $("#filterName").val(); - algorithm = $("#filterAlgorithm option:selected").val(); - match = $("#filterMatch").val(); - isWhitelist = $("#filterWhitelist").prop("checked"); - isInsensitive = $("#filterInsensitive").prop("checked"); - guildId = selectedServer.id; - - var filterPrimaryKey = await saveFilter(id, name, algorithm, match, isWhitelist, isInsensitive, guildId); - - if (filterPrimaryKey) { - showToast("success", "Filter Saved", "Filter ID " + filterPrimaryKey); - await loadFilters(guildId); - await loadFilterOptions(guildId); - } - - $("#filterFormModal").modal("hide"); -}); - -async function saveFilter(id, name, algorithm, match, isWhitelist, isInsensitive, guildId) { - var formData = new FormData(); - formData.append("name", name); - formData.append("matching_algorithm", algorithm); - formData.append("match", match); - formData.append("is_whitelist", isWhitelist); - formData.append("is_insensitive", isInsensitive); - formData.append("guild_id", guildId); - - var response; - - try { - if (id === "-1") response = await newFilter(formData); - else response = await editFilter(id, formData); - } - catch (err) { - showToast("danger", "Filter Error", err.responseText, 18000); - return false - } - - return response.id; -} - -function clearExistingFilterRows() { - $("#filtersTable thead .table-select-all").prop("checked", false).prop("indeterminate", false) - filtersTable.clear().draw(false) -} - -$("#filtersTabPane").on("click", ".table-refresh-btn", async function() { - loadFilters(selectedServer.id); -}); - -async function loadFilters(guildId) { - if (!guildId) - return; - - setTableFilter("filtersTable", "guild_id", guildId); - ensureTablePagination("filtersTable"); - - $("#filtersTabPane .table-del-btn").prop("disabled", true); - clearExistingFilterRows(); - - try { - var contentFilters = await getFilters(tableFilters["filtersTable"], tableSorts["filtersTable"]); - filtersTable.rows.add(contentFilters.results).draw(false); - } - catch (err) { - console.error(err) - showToast("danger", `Error Loading Filters: HTTP ${err.status}`, err, 15000); - return; - } - - updateTableContainer( - "filtersTabPane", - tableFilters["filtersTable"]["page"], - tableFilters["filtersTable"]["page_size"], - contentFilters.results.length, - contentFilters.count, - contentFilters.next, - contentFilters.previous - ); - - $("#filtersTable thead .table-select-all").prop("disabled", contentFilters.results.length === 0); - console.debug(`loaded filters, ${contentFilters.results.length} found`); -} - -$(document).ready(async function() { - await loadMatchingAlgorithms(); -}); - -async function loadMatchingAlgorithms() { - // Disable input while options are loading - $("#filterAlgorithm").prop("disabled", true); - - // Delete existing options - $("#filterAlgorithm option").each(function() { - if ($(this).val()) - $(this).remove(); - }); - - // Clear select2 input - $("#filterAlgorithm").val("").change(); - - try { - options = await getFilterOptions(); - options.actions.POST.matching_algorithm.choices.forEach(algorithm => { - $("#filterAlgorithm").append($("
${url}`; - } - }, - { - title: "Channels", - data: "channels_count", - className: "text-center", - render: function(data) { - const channelsCount = sanitise(data); - return `${channelsCount}`; - } - }, - { - title: "Content Rules", - data: "unique_content_rules", - className: "text-center text-nowrap", - render: function(data, type) { - console.log(JSON.stringify(data)) - - let badges = $("
"); - - data.forEach(function(rule, idx) { - let badge = $(`${rule.name}`) - if (idx > 0) { badge.addClass("ms-2") } - badges.append(badge); - }); - - return badges.html(); - } - }, - { - title: "Created", - data: "creation_datetime", - render: function(data, type) { - let dateTime = new Date(data); - return $(` - %H:%M:%S")}"> - ${formatStringDate(dateTime, "%D, %b %Y")} - - `).popover()[0]; - } - }, - { - title: "Notes", - data: "extra_notes", - orderable: false, - className: "text-center", - render: function(data, type) { - if (!data) { return "" } - const extraNotes = sanitise(data); - return $(` - - - `).popover()[0]; - } - }, - { - title: "Active", - data: "active", - orderable: false, - className: "text-center form-switch", - render: function(data, type) { - return `` - } - }, - { - orderable: false, - className: "p-0", - render: function(data, type, row) { - const embedColour = sanitise(row.embed_colour); - return `
 
` - } - } - ] - }); - - bindTableCheckboxes("#subTable", subTable, "#subscriptionsTabPane .table-del-btn"); -} - -async function updateSubFromObject(sub, handleErrorMsg=true) { - let data = { - "name": sub.name, - "url": sub.url, - "guild_id": sub.guild_id, - "extra_notes": sub.extra_notes, - "embed_colour": sub.embed_colour, - "article_fetch_image": sub.article_fetch_image, - "published_threshold": sub.published_threshold, - "active": sub.active - }; - - let formData = new FormData(); - - for (key in data) { - formData.append(key, data[key]); - } - - sub.article_title_mutators.forEach(mutator => formData.append("article_title_mutators", mutator.id)); - sub.article_desc_mutators.forEach(mutator => formData.append("article_desc_mutators", mutator.id)); - sub.filters.forEach(filter => formData.append("filters", filter)); - - return await saveSubscription(sub.id, formData, handleErrorMsg=handleErrorMsg); -} - -$("#subscriptionsTabPane").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 subId = await updateSubFromObject(sub, handleErrorMsg=false); - - if (!subId) { - throw Error("This subscription no longer exists."); - } - - showToast( - active ? "success" : "danger", - "Subscription " + (active ? "Activated" : "Deactivated"), - "Subscription ID: " + subId - ); - } - catch (error) { - console.error(error); - showToast( - "danger", - "Error Updating Subscription", - `Tried to toggle activeness, but encountered a problem.
${error}` - ); - } - 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); -}); - -function clearPreviousValidation() { - $("#subFormModal, #subAdvancedModal").removeClass("was-validated"); - $("#subFormModal .invalid-feedback, #subAdvancedModal .invalid-feedback").remove(); - $("#subFormModal .is-invalid, #subAdvancedModal .is-invalid").removeClass("is-invalid"); -} - -async function showEditSubModal(subId) { - clearPreviousValidation(); - - 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); - $("#subUniqueRules").val(1).change(); // GUID option selected by default - - $("#subEmbedColour .colour-reset").click(); - $("#subArticleFetchImage").prop("checked", true); - - $("#subPubThreshold").val(getCurrentDateTime()); - } - 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(); - - $("#subUniqueRules").val("").change(); - $("#subUniqueRules").val(subscription.unique_content_rules.map(rule => rule.id)).change(); - - updateColourInput("subEmbedColour", `#${subscription.embed_colour}`); - $("#subArticleFetchImage").prop("checked", subscription.article_fetch_image); - - $("#subPubThreshold").val(subscription.published_threshold.split('+')[0]); - } - - $("#subId").val(subId); - $("#subFormModal").modal("show"); -} - -function getValueFromField(elem) { - const tagName = elem.tagName.toLowerCase(); - const $elem = $(elem); - - if (tagName) { return $elem.val() } - - switch ($elem.attr("type")) { - case "checkbox": - return $elem.prop("checked"); - - default: - return $elem.val(); - } -} - -$("#subForm").on("submit", async function(event) { - event.preventDefault(); - - let subId = $("#subId").val(); - let guildId = selectedServer.id; - - // TODO: move this into a function, so I can fix the active toggle switches which are broken due to this change - - let formData = new FormData(); - formData.append("guild_id", guildId); - - // Populate formdata with [data-field] control values - $('#subForm [data-field], #subAdvancedModal [data-field]').each(function() { - const value = getValueFromField(this); - if (Array.isArray(value) && !value.length) { return }; - formData.append($(this).data("field"), value); - }); - - // Add title mutators to formdata - $("#subTitleMutators option:selected").toArray().map(mutator => parseInt(mutator.value)).forEach( - mutator => formData.append("article_title_mutators", mutator) - ); - - // Add description mutator to formdata - $("#subDescMutators option:selected").toArray().map(mutator => parseInt(mutator.value)).forEach( - mutator => formData.append("article_desc_mutators", mutator) - ); - - // Add Filters to formdata - $("#subFilters option:selected").toArray().forEach( - filter => formData.append("filters", parseInt(filter.value)) - ); - - for (const [key, value] of formData.entries()) { - console.log(`${key}: ${value}`); - } - - // // Unique Content Rules - // $("#subUniqueRules option:selected").toArray().forEach( - // rule => formData.append("unique_content_rules", parseInt(rule.value)) - // ); - - for (const [key, value] of formData.entries()) { - console.log(`${key}: ${value}`); - } - - // This field is constructed differently, so needs to be specifically added - formData.append("embed_colour", getColourInputVal("subEmbedColour", false)); - - - subId = await saveSubscription(subId, formData); - - if (subId) { - showToast("success", "Subscription Saved", `Subscription ID ${subId}`); - } - else { - showToast("danger", "Error Saving Subscription", ""); - return; - } - - await deleteSubChannels(subId); - $("#subChannels option:selected").each(async function() { - let $channel = $(this); - let channelFormData = new FormData(); - channelFormData.append("channel_id", $channel.val()); - channelFormData.append("channel_name", $channel.data("name")); - channelFormData.append("subscription", subId); - await newSubChannel(channelFormData); - }); - - await loadSubscriptions(guildId); - $("#subFormModal").modal("hide"); -}); - -async function saveSubscription(id, formData, handleErrorMsg=true) { - let response - - try { - response = id === "-1" ? await newSubscription(formData) : await editSubscription(id, formData); - } - catch (err) { - if (typeof err !== "object" && err.responseJSON) { - return false - } - - clearPreviousValidation(); - for (const [fieldKey, message] of Object.entries(err.responseJSON)) { - const $field = $(`#subFormModal [data-field="${fieldKey}"], #subAdvancedModal [data-field="${fieldKey}"]`); - const $helpText = $field.closest("div").find(".form-text"); - const $feedback = $(`
${message}
`); - - $field.addClass("is-invalid").prop("invalid", true); - $feedback.insertAfter($helpText.length ? $helpText : $field); - } - - $("#subFormModal, #subAdvancedModal").addClass("was-validated"); - - if (handleErrorMsg) { - showToast("danger", "Subscription Error", err.responseText, 18000); - } - - return false; - } - - return response.id; -} - -async function saveSubChannel(formData) { - 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); -} - -$("#subscriptionsTabPane").on("click", ".table-refresh-btn", async function() { - loadSubscriptions(selectedServer.id); -}); - -async function loadSubscriptions(guildId) { - if (!guildId) - return; - - setTableFilter("subTable", "guild_id", guildId); - ensureTablePagination("subTable"); - - $("#subscriptionsTabPane .table-del-btn").prop("disabled", true); - clearExistingSubRows(); - - try { - var subs = await getSubscriptions(tableFilters["subTable"], tableSorts["subTable"]); - subTable.rows.add(subs.results).draw(false); - } - catch (err) { - console.error(err) - showToast("danger", `Error Loading Subscriptions: HTTP ${err.status}`, err, 15000); - return; - } - - updateTableContainer( - "subscriptionsTabPane", - tableFilters["subTable"]["page"], - tableFilters["subTable"]["page_size"], - subs.results.length, - subs.count, - subs.next, - subs.previous - ); - - $("#subTable thead .table-select-all").prop("disabled", subs.results.length === 0); - console.debug("loading subs, " + subs.results.length + " found"); -} - -// #region Server Change Event Handler - -$(document).on("selectedServerChange", async function() { - let guildId = selectedServer.id; - - // await updateDefaultSubEmbedColour(); - - await loadSubscriptions(guildId); - await loadChannelOptions(guildId); - await loadFilterOptions(guildId); - await loadMutatorOptions(); - await loadUniqueContentRuleOptions(); -}) - -async function updateDefaultSubEmbedColour(settings=null) { - if (!settings){ - settings = (await getGuildSettings(selectedServer.id)).results[0] - } - $("#subEmbedColour .colour-reset").attr("data-defaultcolour", "#" + settings.default_embed_colour); -} - -// #endregion - - -// #region Delete Subscriptions - -// Delete button on the 'edit subscription' modal -$("#deleteEditSub").on("click", async function() { - const subId = parseInt($("#subId").val()); - const sub = subTable.row(function(idx, row) { return row.id === subId }).data(); - const subName = sanitise(sub.name); - - $("#subFormModal").modal("hide"); - - await confirmationModal( - "Delete a Subscription", - `Do you wish to permanently delete ${subName}?`, - "danger", - async () => { - await deleteSubscription(subId); - await loadSubscriptions(selectedServer.id); - - showToast( - "danger", - "Deleted a Subscription", - subName, - 12000 - ); - }, - async () => { - $("#subFormModal").modal("show"); - } - ); -}); - -async function deleteSelectedSubscriptions() { - const rows = subTable.rows(".selected").data().toArray(); - const names = rows.map(row => row.name); - const namesString = arrayToHtmlList(names, true).prop("outerHTML"); - const isMany = names.length > 1; - - await confirmationModal( - `Delete ${isMany ? "Many Subscriptions" : "a Subscription"}`, - `Do you wish to permanently delete ${isMany ? "these" : "this"} ${names.length} subscription${isMany ? "s" : ""}?

${namesString}`, - "danger", - async () => { - rows.forEach(async row => { await deleteSubscription(row.id) }); - - showToast( - "danger", - `Deleted ${names.length} Subscription${isMany ? "s" : ""}`, - `${arrayToHtmlList(names, false).prop("outerHTML")}`, - 12000 - ); - - // Multi-deletion can take time, this timeout ensures the refresh is accurate - setTimeout(async () => { - await loadSubscriptions(selectedServer.id); - }, 600); - }, - null - - ); -} - -// #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); - - // 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) { - let server = getServerFromSnowflake(guildId); - - // Also check that the user hasn't changed the currently active guild, otherwise - // the alert will show under the wrong server. - if (selectedServer.id === guildId) - $("#serverJoinAlert").show(); - - // Warning icon on sidebar server select - let sidebarItem = $(`#serverList .server-item[data-guild-id="${guildId}"]`); - if (!sidebarItem.find(".badge.text-warning").length) { - let alertTemplate = $($("#serverItemIconTemplate").html()); - alertTemplate.attr("data-bs-title", `The Bot isn't a member of ${sanitise(server.name)}`).tooltip(); - sidebarItem.find(".server-item-selector").append(alertTemplate); - } - - const guildName = sanitise(getServerFromSnowflake(guildId).name); - - throw new Error( - `Unable to retrieve channels from Guild ${guildName}. - Ensure that @PYRSS is a member with permissions - to view channels.` - ); - } - - // Sort by the specified position of each channel object - channels.sort((a, b) => a.position - b.position); - - discordChannels = []; - channels.forEach(channel => { - - // We only want TextChannels, which have a type of 0 - if (channel.type !== 0) - return; - - let channelObj = {text: `#${channel.name}`, value: channel.id, "data-name": channel.name} - $("#subChannels").append($("