- // ==UserScript==
- // @name AniList Edit Multiple Media Simultaneously
- // @license MIT
- // @namespace rtonne
- // @match https://anilist.co/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=anilist.co
- // @version 1.0
- // @author Rtonne
- // @description Adds the ability to select multiple manga/anime in your lists and act on them simultaneously
- // @grant GM.getResourceText
- // @grant GM.addStyle
- // @require https://update.greasyfork.org/scripts/496874/1387742/components.js
- // @require https://update.greasyfork.org/scripts/496875/1387743/helpers.js
- // @resource GLOBAL_CSS https://raw.githubusercontent.com/p-laranjinha/userscripts/master/AniList%20Edit%20Multiple%20Media%20Simultaneously/global.css
- // @resource PLUS_SVG https://raw.githubusercontent.com/p-laranjinha/userscripts/master/AniList%20Edit%20Multiple%20Media%20Simultaneously/plus.svg
- // @resource MINUS_SVG https://raw.githubusercontent.com/p-laranjinha/userscripts/master/AniList%20Edit%20Multiple%20Media%20Simultaneously/minus.svg
- // ==/UserScript==
-
- // REPLACE THE @require AND @resource WITH THE FOLLOWING DURING DEVELOPMENT
- // AND REMEMBER TO UPDATE THE REQUIRES
- // @require components.js
- // @require helpers.js
- // @resource GLOBAL_CSS global.css
- // @resource PLUS_SVG plus.svg
- // @resource MINUS_SVG minus.svg
-
- const GLOBAL_CSS = GM.getResourceText("GLOBAL_CSS");
- GM.addStyle(GLOBAL_CSS);
- const PLUS_SVG = GM.getResourceText("PLUS_SVG");
- const MINUS_SVG = GM.getResourceText("MINUS_SVG");
-
- let WAS_LAST_LIST_ANIME = false;
-
- let current_url = null;
- let new_url = null;
-
- const url_regex =
- /^https:\/\/anilist.co\/user\/.+\/((animelist)|(mangalist))(\/.*)?$/;
-
- // Using observer to run script whenever the body changes
- // because anilist doesn't reload when changing page
- const observer = new MutationObserver(async () => {
- try {
- new_url = window.location.href;
-
- // Because anilist doesn't reload on changing url
- // we have to allow the whole website and check here if we are in a list
- if (!url_regex.test(new_url)) {
- return;
- }
-
- // If we have actions in the banner, it's not our list and can't edit it
- if (
- (await waitForElements(".banner-content .actions"))[0].children.length > 0
- ) {
- return;
- }
-
- setupButtons();
- setupForm();
- } catch (err) {
- console.error(err);
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- });
-
- async function setupButtons() {
- const entries = await waitForElements(".entry, .entry-card");
-
- // If the url is different we are in a different list
- // Or if the list length is different, we loaded more of the same list
- if (
- current_url === new_url &&
- entries.length ===
- document.querySelectorAll(".rtonne-anilist-multiselect-addbutton").length
- ) {
- return;
- }
-
- current_url = new_url;
-
- let isCard = false;
- if (entries.length > 0 && entries[0].classList.contains("entry-card")) {
- isCard = true;
- }
- entries.forEach((entry) => {
- const cover = entry.querySelector(".cover");
-
- // We return if the item already has a select button so
- // there isn't an infinite loop where adding a button triggers
- // the observer which adds more buttons
- if (entry.querySelector(".rtonne-anilist-multiselect-addbutton")) return;
-
- const add_button = document.createElement("div");
- add_button.className = "rtonne-anilist-multiselect-addbutton edit";
- add_button.innerHTML = PLUS_SVG;
- // I'm appending the buttons to the cards in a different place so I can have them above long titles
- if (isCard) {
- entry.append(add_button);
- } else {
- cover.querySelector(".edit").after(add_button);
- }
- const remove_button = document.createElement("div");
- remove_button.className = "rtonne-anilist-multiselect-removebutton edit";
- remove_button.innerHTML = MINUS_SVG;
- add_button.after(remove_button);
-
- add_button.onclick = () => {
- entry.className += " rtonne-anilist-multiselect-selected";
- };
-
- remove_button.onclick = () => {
- entry.classList.remove("rtonne-anilist-multiselect-selected");
- };
- });
- }
-
- async function setupForm() {
- // Check if the form needs to be made/remade
- const [container] = await waitForElements(".filters-wrap");
- const is_list_anime = document
- .querySelector(".nav.container > a[href$='animelist']")
- .classList.contains("router-link-active");
- const previous_forms = document.querySelectorAll(
- ".rtonne-anilist-multiselect-form"
- );
- const previous_helps = document.querySelectorAll(
- ".rtonne-anilist-multiselect-form-help"
- );
- if (previous_forms.length > 0) {
- // In case we end up with multiple forms because of asynchronicity, remove the extra ones
- if (previous_forms.length > 1) {
- for (let i = 0; i < previous_forms.length - 1; i++) {
- previous_forms[i].remove();
- previous_helps[i].remove();
- }
- }
- // If we change from anime to manga or vice versa, redo the form
- if (WAS_LAST_LIST_ANIME !== is_list_anime) {
- for (let i = 0; i < previous_forms.length; i++) {
- previous_forms[i].remove();
- previous_helps[i].remove();
- }
- } else {
- return;
- }
- }
- WAS_LAST_LIST_ANIME = is_list_anime;
-
- // Choose what status and score to use in the form
- let status_options = [
- "Reading",
- "Plan to read",
- "Completed",
- "Rereading",
- "Paused",
- "Dropped",
- ];
- if (is_list_anime) {
- status_options = [
- "Watching",
- "Plan to read",
- "Completed",
- "Rewatching",
- "Paused",
- "Dropped",
- ];
- }
-
- let score_step = 1,
- score_max;
- const [element_with_score_type] = await waitForElements(
- ".content.container > .medialist"
- );
- if (element_with_score_type.classList.contains("POINT_10_DECIMAL")) {
- score_step = 0.5;
- score_max = 10;
- } else if (element_with_score_type.classList.contains("POINT_100")) {
- score_max = 100;
- } else if (element_with_score_type.classList.contains("POINT_10")) {
- score_max = 10;
- } else if (element_with_score_type.classList.contains("POINT_5")) {
- score_max = 5;
- } else {
- // if (element_with_score_type.classList.contains("POINT_3"))
- score_max = 3;
- }
-
- // Create the form container
- let previous_form = document.querySelector(
- ".rtonne-anilist-multiselect-form"
- );
- if (previous_form) {
- return;
- }
- const form = document.createElement("div");
- form.className = "rtonne-anilist-multiselect-form";
- form.style.display = "none";
- container.append(form);
-
- // We get custom_lists and advanced_scores after creating the form so we can do it only once
- let custom_lists = [];
- while (true) {
- const first_media_id = Number(
- document
- .querySelector(".entry .title > a, .entry-card .title > a")
- .href.split("/")[4]
- );
- const custom_lists_response = await getDataFromEntries(
- [first_media_id],
- "customLists"
- );
- if (custom_lists_response.errors) {
- const error_message = `An error occurred while getting the available custom lists. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- document.body.className += " rtonne-anilist-multiselect-form-failed";
- return;
- }
- } else {
- custom_lists = custom_lists_response.data[0]
- ? Object.keys(custom_lists_response.data[0])
- : [];
- break;
- }
- }
- let advanced_scores = [];
- while (true) {
- const first_media_id = Number(
- document
- .querySelector(".entry .title > a, .entry-card .title > a")
- .href.split("/")[4]
- );
- const is_advanced_scores_enabled = await isAdvancedScoringEnabled();
- if (is_advanced_scores_enabled.errors) {
- const error_message = `An error occurred while getting if advanced scores are enabled. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- document.body.className += " rtonne-anilist-multiselect-form-failed";
- return;
- }
- } else if (
- (is_list_anime && is_advanced_scores_enabled.data.anime) ||
- (!is_list_anime && is_advanced_scores_enabled.data.manga)
- ) {
- const advanced_scores_response = await getDataFromEntries(
- [first_media_id],
- "advancedScores"
- );
- if (advanced_scores_response.errors) {
- const error_message = `An error occurred while getting the available advanced scores. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- document.body.className += " rtonne-anilist-multiselect-form-failed";
- return;
- }
- } else {
- advanced_scores = advanced_scores_response.data[0]
- ? Object.keys(advanced_scores_response.data[0])
- : [];
- break;
- }
- } else {
- break;
- }
- }
-
- // Create the form contents
- const help = document.createElement("div");
- help.className = "rtonne-anilist-multiselect-form-help";
- help.innerHTML =
- "ⓘ Because values can be empty, there are 2 ways to enable them. The first one is via an Enable checkbox;" +
- " the second one is using indeterminate checkboxes, where a dark square and strikethrough text means they're not enabled." +
- "<br>ⓘ Batch updating is done whenever possible. The following cases require individual updates:" +
- " choosing some but not all advanced scores; choosing one or more custom lists; adding or removing from favourites; deleting.";
- help.style.width = "100%";
- help.style.paddingTop = "20px";
- help.style.fontSize = "smaller";
- help.style.display = "none";
- form.after(help);
-
- const status_container = document.createElement("div");
- status_container.id = "rtonne-anilist-multiselect-status-input";
- status_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(status_container);
- const status_label = document.createElement("label");
- status_label.innerText = "Status";
- status_container.append(status_label);
- const status_enabled_checkbox = createCheckbox(status_container, "Enabled");
- const status_input = createSelectInput(status_container, status_options);
-
- const score_container = document.createElement("div");
- score_container.id = "rtonne-anilist-multiselect-score-input";
- score_container.className = "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(score_container);
- const score_label = document.createElement("label");
- score_label.innerText = "Score";
- score_container.append(score_label);
- const score_enabled_checkbox = createCheckbox(score_container, "Enabled");
- const score_input = createNumberInput(score_container, score_max, score_step);
-
- /** @type {HTMLInputElement[]} */
- let advanced_scores_enabled_checkboxes = [];
- /** @type {HTMLInputElement[]} */
- let advanced_scores_inputs = [];
- if (advanced_scores.length > 0) {
- for (const advanced_score of advanced_scores) {
- const advanced_score_container = document.createElement("div");
- advanced_score_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(advanced_score_container);
- const advanced_score_label = document.createElement("label");
- advanced_score_label.innerHTML = `${advanced_score} <small>(Advanced Score)</small>`;
- advanced_score_label.style.wordBreak = "break-all";
- advanced_score_container.append(advanced_score_label);
- advanced_scores_enabled_checkboxes.push(
- createCheckbox(advanced_score_container, "Enabled")
- );
- advanced_scores_inputs.push(
- createNumberInput(advanced_score_container, 100, 0)
- );
- }
- }
-
- /**
- * Collection of progress inputs.
- * Changes depending on if the list is for anime or manga.
- * @type {{
- * episode_enabled_checkbox: HTMLInputElement,
- * episode_input: HTMLInputElement,
- * rewatches_enabled_checkbox: HTMLInputElement,
- * rewatches_input: HTMLInputElement,
- * } | {
- * chapter_enabled_checkbox: HTMLInputElement,
- * chapter_input: HTMLInputElement,
- * volume_enabled_checkbox: HTMLInputElement,
- * volume_input: HTMLInputElement,
- * rereads_enabled_checkbox: HTMLInputElement,
- * rereads_input: HTMLInputElement,
- * }}
- */
- const progress_inputs = (() => {
- const result = {};
- if (is_list_anime) {
- const episode_container = document.createElement("div");
- episode_container.id = "rtonne-anilist-multiselect-episode-input";
- episode_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(episode_container);
- const episode_label = document.createElement("label");
- episode_label.innerText = "Episode Progress";
- episode_container.append(episode_label);
- result.episode_enabled_checkbox = createCheckbox(
- episode_container,
- "Enabled"
- );
- result.episode_input = createNumberInput(episode_container);
-
- const rewatches_container = document.createElement("div");
- rewatches_container.id = "rtonne-anilist-multiselect-rewatches-input";
- rewatches_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(rewatches_container);
- const rewatches_label = document.createElement("label");
- rewatches_label.innerText = "Total Rewatches";
- rewatches_container.append(rewatches_label);
- result.rewatches_enabled_checkbox = createCheckbox(
- rewatches_container,
- "Enabled"
- );
- result.rewatches_input = createNumberInput(rewatches_container);
- } else {
- const chapter_container = document.createElement("div");
- chapter_container.id = "rtonne-anilist-multiselect-episode-input";
- chapter_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(chapter_container);
- const chapter_label = document.createElement("label");
- chapter_label.innerText = "Chapter Progress";
- chapter_container.append(chapter_label);
- result.chapter_enabled_checkbox = createCheckbox(
- chapter_container,
- "Enabled"
- );
- result.chapter_input = createNumberInput(chapter_container);
-
- const volume_container = document.createElement("div");
- volume_container.id = "rtonne-anilist-multiselect-episode-input";
- volume_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(volume_container);
- const volume_label = document.createElement("label");
- volume_label.innerText = "Volume Progress";
- volume_container.append(volume_label);
- result.volume_enabled_checkbox = createCheckbox(
- volume_container,
- "Enabled"
- );
- result.volume_input = createNumberInput(volume_container);
-
- const rereads_container = document.createElement("div");
- rereads_container.id = "rtonne-anilist-multiselect-rewatches-input";
- rereads_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(rereads_container);
- const rereads_label = document.createElement("label");
- rereads_label.innerText = "Total Rereads";
- rereads_container.append(rereads_label);
- result.rereads_enabled_checkbox = createCheckbox(
- rereads_container,
- "Enabled"
- );
- result.rereads_input = createNumberInput(rereads_container);
- }
- return result;
- })();
-
- const start_date_container = document.createElement("div");
- start_date_container.id = "rtonne-anilist-multiselect-start-date-input";
- start_date_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(start_date_container);
- const start_date_label = document.createElement("label");
- start_date_label.innerText = "Start Date";
- start_date_container.append(start_date_label);
- const start_date_enabled_checkbox = createCheckbox(
- start_date_container,
- "Enabled"
- );
- const start_date_input = createDateInput(start_date_container);
-
- const finish_date_container = document.createElement("div");
- finish_date_container.id = "rtonne-anilist-multiselect-finish-date-input";
- finish_date_container.className =
- "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(finish_date_container);
- const finish_date_label = document.createElement("label");
- finish_date_label.innerText = "Finish Date";
- finish_date_container.append(finish_date_label);
- const finish_date_enabled_checkbox = createCheckbox(
- finish_date_container,
- "Enabled"
- );
- const finish_date_input = createDateInput(finish_date_container);
-
- const notes_container = document.createElement("div");
- notes_container.id = "rtonne-anilist-multiselect-notes-input";
- notes_container.className = "rtonne-anilist-multiselect-has-enabled-checkbox";
- form.append(notes_container);
- const notes_label = document.createElement("label");
- notes_label.innerText = "Notes";
- notes_container.append(notes_label);
- const notes_enabled_checkbox = createCheckbox(notes_container, "Enabled");
- const notes_input = createTextarea(notes_container);
-
- /** @type {HTMLInputElement|null} */
- let hide_from_status_list_checkbox;
- /** @type {HTMLInputElement[]} */
- let custom_lists_checkboxes = [];
- if (custom_lists.length > 0) {
- const custom_lists_container = document.createElement("div");
- custom_lists_container.id = "rtonne-anilist-multiselect-custom-lists-input";
- form.append(custom_lists_container);
- const custom_lists_label = document.createElement("label");
- custom_lists_label.innerText = "Custom Lists";
- custom_lists_container.append(custom_lists_label);
-
- for (const custom_list of custom_lists) {
- custom_lists_checkboxes.push(
- createIndeterminateCheckbox(custom_lists_container, custom_list)
- );
- }
-
- const custom_lists_separator = document.createElement("div");
- custom_lists_separator.style.width = "100%";
- custom_lists_separator.style.marginBottom = "6px";
- custom_lists_separator.style.borderBottom =
- "solid 1px rgba(var(--color-text-lighter),.3)";
- custom_lists_container.append(custom_lists_separator);
- hide_from_status_list_checkbox = createIndeterminateCheckbox(
- custom_lists_container,
- "Hide from status lists"
- );
- }
-
- const other_actions_container = document.createElement("div");
- other_actions_container.id = "rtonne-anilist-multiselect-other-actions-input";
- form.append(other_actions_container);
- const other_actions_label = document.createElement("label");
- other_actions_label.innerText = "Other Actions";
- other_actions_container.append(other_actions_label);
- const private_checkbox = createIndeterminateCheckbox(
- other_actions_container,
- "Private"
- );
- const favourite_checkbox = createIndeterminateCheckbox(
- other_actions_container,
- "Favourite"
- );
- const delete_checkbox = createCheckbox(other_actions_container, "Delete");
-
- const deselect_all_button = createDangerButton(form, "Deselect All Entries");
-
- const confirm_button = createButton(form, "Confirm");
- new MutationObserver(() => {
- if (
- delete_checkbox.checked ||
- status_enabled_checkbox.checked ||
- (advanced_scores.length > 0 &&
- advanced_scores_enabled_checkboxes.some((e) => e.checked)) ||
- score_enabled_checkbox.checked ||
- (is_list_anime &&
- (progress_inputs.episode_enabled_checkbox.checked ||
- progress_inputs.rewatches_enabled_checkbox.checked)) ||
- (!is_list_anime &&
- (progress_inputs.chapter_enabled_checkbox.checked ||
- progress_inputs.volume_enabled_checkbox.checked ||
- progress_inputs.rereads_enabled_checkbox.checked)) ||
- start_date_enabled_checkbox.checked ||
- finish_date_enabled_checkbox.checked ||
- notes_enabled_checkbox.checked ||
- (custom_lists.length > 0 &&
- (custom_lists_checkboxes.some((e) => !e.indeterminate) ||
- !hide_from_status_list_checkbox.indeterminate)) ||
- !private_checkbox.indeterminate ||
- !favourite_checkbox.indeterminate
- ) {
- confirm_button.style.display = "unset";
- } else {
- confirm_button.style.display = "none";
- }
- }).observe(form, {
- childList: true,
- subtree: true,
- attributeFilter: ["class"],
- });
-
- const currently_selected_label = document.createElement("label");
- currently_selected_label.style.alignSelf = "center";
- currently_selected_label.style.color = "rgb(var(--color-blue))";
- form.append(currently_selected_label);
-
- deselect_all_button.onclick = () => {
- const selected_entries = document.querySelectorAll(
- ".entry.rtonne-anilist-multiselect-selected, .entry-card.rtonne-anilist-multiselect-selected"
- );
- for (const entry of selected_entries) {
- entry.classList.remove("rtonne-anilist-multiselect-selected");
- }
- };
-
- confirm_button.onclick = () => {
- let action_list = "";
- let values_to_be_changed = {};
- if (!delete_checkbox.checked) {
- if (status_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Status</u> to <b>${status_input.value}</b>.</li>`;
- switch (status_input.value) {
- case "Reading":
- case "Watching":
- values_to_be_changed.status = "CURRENT";
- break;
- case "Plan to read":
- values_to_be_changed.status = "PLANNING";
- break;
- case "Completed":
- values_to_be_changed.status = "COMPLETED";
- break;
- case "Rereading":
- case "Rewatching":
- values_to_be_changed.status = "REPEATING";
- break;
- case "Paused":
- values_to_be_changed.status = "PAUSED";
- break;
- case "Dropped":
- values_to_be_changed.status = "DROPPED";
- break;
- }
- }
- if (score_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Score</u> to <b>${score_input.value}</b>.</li>`;
- values_to_be_changed.score = Number(score_input.value);
- }
- if (advanced_scores.length > 0) {
- // Create array with advanced_scores.length count of null
- values_to_be_changed.advancedScores = Array.from(
- { length: advanced_scores.length },
- () => null
- );
- for (let i = 0; i < advanced_scores.length; i++) {
- if (advanced_scores_enabled_checkboxes[i].checked) {
- action_list += `<li>Set the <u>${advanced_scores[i]}</u> <u>Advanced Score</u> to <b>${advanced_scores_inputs[i].value}</b>.</li>`;
- values_to_be_changed.advancedScores[i] = Number(
- advanced_scores_inputs[i].value
- );
- }
- }
- }
- if (is_list_anime) {
- if (progress_inputs.episode_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Episode Progress</u> to <b>${progress_inputs.episode_input.value}</b>.</li>`;
- values_to_be_changed.progress = Number(
- progress_inputs.episode_input.value
- );
- }
- if (progress_inputs.rewatches_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Total Rewatches</u> to <b>${progress_inputs.rewatches_input.value}</b>.</li>`;
- values_to_be_changed.repeat = Number(
- progress_inputs.rewatches_input.value
- );
- }
- } else {
- if (progress_inputs.chapter_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Chapter Progress</u> to <b>${progress_inputs.chapter_input.value}</b>.</li>`;
- values_to_be_changed.progress = Number(
- progress_inputs.chapter_input.value
- );
- }
- if (progress_inputs.volume_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Volume Progress</u> to <b>${progress_inputs.volume_input.value}</b>.</li>`;
- values_to_be_changed.progressVolume = Number(
- progress_inputs.volume_input.value
- );
- }
- if (progress_inputs.rereads_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Total Rereads</u> to <b>${progress_inputs.rereads_input.value}</b>.</li>`;
- values_to_be_changed.repeat = Number(
- progress_inputs.rereads_input.value
- );
- }
- }
- if (start_date_enabled_checkbox.checked) {
- const date = {
- year: start_date_input.value.split("-")[0],
- month: start_date_input.value.split("-")[1],
- day: start_date_input.value.split("-")[2],
- };
-
- if (!date.year || !date.month || !date.day) {
- action_list += `<li>Set <u>Start Date</u> to <b>nothing</b>.</li>`;
- values_to_be_changed.startedAt = {};
- } else {
- action_list += `<li>Set <u>Start Date</u> to <b>${start_date_input.value}</b>.</li>`;
- values_to_be_changed.startedAt = date;
- }
- }
- if (finish_date_enabled_checkbox.checked) {
- const date = {
- year: finish_date_input.value.split("-")[0],
- month: finish_date_input.value.split("-")[1],
- day: finish_date_input.value.split("-")[2],
- };
-
- if (!date.year || !date.month || !date.day) {
- action_list += `<li>Set <u>Finish Date</u> to <b>nothing</b>.</li>`;
- values_to_be_changed.completedAt = {};
- } else {
- action_list += `<li>Set <u>Finish Date</u> to <b>${finish_date_input.value}</b>.</li>`;
- values_to_be_changed.completedAt = date;
- }
- }
- if (notes_enabled_checkbox.checked) {
- action_list += `<li>Set <u>Notes</u> to <b>${notes_input.value}</b>.</li>`;
- values_to_be_changed.notes = notes_input.value;
- }
- if (custom_lists.length > 0) {
- for (let i = 0; i < custom_lists.length; i++) {
- if (!custom_lists_checkboxes[i].indeterminate) {
- if (!values_to_be_changed.customLists) {
- values_to_be_changed.customLists = [];
- }
- if (custom_lists_checkboxes[i].checked) {
- action_list += `<li>Add to the <b>${custom_lists[i]}</b> <u>Custom List</u>.</li>`;
- values_to_be_changed.customLists.push(custom_lists[i]);
- } else {
- action_list += `<li>Remove from the <b>${custom_lists[i]}</b> <u>Custom List</u>.</li>`;
- }
- }
- }
- if (!hide_from_status_list_checkbox.indeterminate) {
- if (hide_from_status_list_checkbox.checked) {
- action_list += `<li><u><b>Hide</b> from status lists.</u></li>`;
- values_to_be_changed.hiddenFromStatusLists = true;
- } else {
- action_list += `<li><u><b>Show</b> on status lists.</u></li>`;
- values_to_be_changed.hiddenFromStatusLists = false;
- }
- }
- }
- if (!private_checkbox.indeterminate) {
- if (private_checkbox.checked) {
- action_list += `<li>Set as <u><b>Private</b></u>.</li>`;
- values_to_be_changed.private = true;
- } else {
- action_list += `<li>Set as <u><b>Public</b></u>.</li>`;
- values_to_be_changed.private = false;
- }
- }
- if (!favourite_checkbox.indeterminate) {
- if (favourite_checkbox.checked) {
- action_list += `<li><b>Add</b> to <u>Favourites</u>.</li>`;
- values_to_be_changed.favourite = true;
- } else {
- action_list += `<li><b>Remove</b> from <u>Favourites</u>.</li>`;
- values_to_be_changed.favourite = false;
- }
- }
- } else {
- values_to_be_changed.delete = true;
- action_list += `<li><u><b>Delete</b></u>.</li>`;
- }
-
- const initial_selected_entries = document.querySelectorAll(
- ".rtonne-anilist-multiselect-selected"
- );
- const confirm_popup_button = createConfirmPopup(
- "Are you sure?",
- `You're about to do the following actions to <b><u>${
- initial_selected_entries.length
- } entr${initial_selected_entries.length > 1 ? "ies" : "y"}</u></b>:
- ${action_list}`
- );
-
- confirm_popup_button.onclick = async () => {
- // It is possible to select the same entry more than once if they're on multiple lists
- // so we need to remove duplicates
- let { selected_entries } = Array.from(initial_selected_entries).reduce(
- (accumulator, currentValue) => {
- const url = currentValue.querySelector(".title > a").href;
- if (accumulator.urls.indexOf(url) < 0) {
- accumulator.urls.push(url);
- accumulator.selected_entries.push(currentValue);
- }
- return accumulator;
- },
- { selected_entries: [], urls: [] }
- );
-
- // Content is in yet another function so I can do stuff after it returns anywhere
- const success = await (async () => {
- let is_cancelled = false;
- const {
- popup_wrapper,
- popup_cancel_button,
- changePopupTitle,
- changePopupContent,
- closePopup,
- } = createUpdatableCancelPopup("Processing the request...", "");
- popup_wrapper.onclick = popup_cancel_button.onclick = () => {
- is_cancelled = true;
- };
-
- let media_ids = [];
- for (const entry of selected_entries) {
- const media_id = Number(
- entry.querySelector(".title > a").href.split("/")[4]
- );
- media_ids.push(media_id);
- }
- let ids_response;
- while (true) {
- ids_response = await getDataFromEntries(media_ids, "id");
- if (ids_response.errors) {
- const error_message = `${ids_response.data.length}/${selected_entries.length} IDs were successfully obtained. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- const ids = ids_response.data;
-
- if (values_to_be_changed.delete) {
- for (let i = 0; i < selected_entries.length && !is_cancelled; i++) {
- const entry_title = selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim();
- changePopupContent(
- createEntryPopupContent(
- `Deleting: <b>${entry_title}</b>`,
- selected_entries[i].querySelector(".image").style
- .backgroundImage,
- i + 1,
- selected_entries.length
- )
- );
- while (true) {
- const delete_response = await deleteEntry(ids[i]);
- if (delete_response.errors) {
- const error_message = `An error occurred while deleting <b>${entry_title}</b>. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- }
- closePopup();
- return true;
- }
- if (values_to_be_changed.favourite !== undefined) {
- let is_favourite_response;
- while (true) {
- is_favourite_response = await getDataFromEntries(
- media_ids,
- "isFavourite"
- );
- if (is_favourite_response.errors) {
- const error_message = `An error occurred while getting info to edit favourites. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- for (let i = 0; i < selected_entries.length && !is_cancelled; i++) {
- const entry_title = selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim();
- if (
- values_to_be_changed.favourite !== is_favourite_response.data[i]
- ) {
- changePopupContent(
- createEntryPopupContent(
- `${
- values_to_be_changed.favourite
- ? "Adding to favourites"
- : "Removing from favourites"
- }: <b>${selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim()}</b>`,
- selected_entries[i].querySelector(".image").style
- .backgroundImage,
- i + 1,
- selected_entries.length
- )
- );
- while (true) {
- let toggle_favourite_response;
- if (is_list_anime) {
- toggle_favourite_response = await toggleFavouriteForEntry({
- animeId: media_ids[i],
- });
- } else {
- toggle_favourite_response = await toggleFavouriteForEntry({
- mangaId: media_ids[i],
- });
- }
- if (toggle_favourite_response.errors) {
- const error_message = `An error occurred while <b>${entry_title}</b> was being ${
- values_to_be_changed.favourite ? "added to" : "removed from"
- } favourites. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- }
- }
- }
-
- // Adding/removing from custom lists requires more meddling.
- // If some but not all custom lists have been chosen further processing is required.
- // array.every() returns true if array is empty so we need to check that.
- /** @type {void | string[][]} */
- let all_processed_custom_lists;
- if (
- custom_lists_checkboxes.some((checkbox) => !checkbox.indeterminate) &&
- !(
- custom_lists_checkboxes.length > 0 &&
- custom_lists_checkboxes.every((checkbox) => !checkbox.indeterminate)
- )
- ) {
- let custom_lists_response;
- while (true) {
- custom_lists_response = await getDataFromEntries(
- media_ids,
- "customLists"
- );
- if (custom_lists_response.errors) {
- const error_message = `An error occurred while getting custom lists. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- all_processed_custom_lists = [];
- for (let i = 0; i < selected_entries.length && !is_cancelled; i++) {
- changePopupContent(
- createEntryPopupContent(
- `Getting the custom lists of: <b>${selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim()}</b>`,
- selected_entries[i].querySelector(".image").style
- .backgroundImage,
- i + 1,
- selected_entries.length
- )
- );
- let processed_custom_lists = [];
- let entry_custom_lists = custom_lists_response.data[i];
- for (let j = 0; j < custom_lists.length; j++) {
- if (!custom_lists_checkboxes[j].indeterminate) {
- if (custom_lists_checkboxes[j].checked) {
- processed_custom_lists.push(custom_lists[j]);
- }
- } else {
- if (entry_custom_lists[custom_lists[j]]) {
- processed_custom_lists.push(custom_lists[j]);
- }
- }
- }
- all_processed_custom_lists.push(processed_custom_lists);
- }
- }
-
- // Using advanced scores requires more meddling.
- // If some but not all advanced scores have been chosen further processing is required.
- // array.every() returns true if array is empty so we need to check that.
- /** @type {void | string[][]} */
- let all_processed_advanced_scores;
- const some_but_not_all_advanced_scores =
- advanced_scores_enabled_checkboxes.some(
- (checkbox) => checkbox.checked
- ) &&
- !(
- advanced_scores_enabled_checkboxes.length > 0 &&
- advanced_scores_enabled_checkboxes.every(
- (checkbox) => checkbox.checked
- )
- );
- if (some_but_not_all_advanced_scores) {
- let advanced_scores_response;
- while (true) {
- advanced_scores_response = await getDataFromEntries(
- media_ids,
- "advancedScores"
- );
- if (advanced_scores_response.errors) {
- const error_message = `An error occurred while getting advanced scores. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- all_processed_advanced_scores = [];
- for (let i = 0; i < selected_entries.length && !is_cancelled; i++) {
- changePopupContent(
- createEntryPopupContent(
- `Getting the advanced scores of: <b>${selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim()}</b>`,
- selected_entries[i].querySelector(".image").style
- .backgroundImage,
- i + 1,
- selected_entries.length
- )
- );
- let processed_advanced_scores = [];
- let entry_advanced_scores = Object.values(
- advanced_scores_response.data[i]
- );
- for (let j = 0; j < advanced_scores.length; j++) {
- if (advanced_scores_enabled_checkboxes[j].checked) {
- processed_advanced_scores.push(
- values_to_be_changed.advancedScores[j]
- );
- } else {
- processed_advanced_scores.push(entry_advanced_scores[j]);
- }
- }
- all_processed_advanced_scores.push(processed_advanced_scores);
- }
- }
-
- // If any custom lists or some but not all advanced scores have been chosen, we require individual updates.
- if (
- custom_lists_checkboxes.some((checkbox) => !checkbox.indeterminate) ||
- some_but_not_all_advanced_scores
- ) {
- const values = { ...values_to_be_changed };
- for (let i = 0; i < selected_entries.length && !is_cancelled; i++) {
- changePopupContent(
- createEntryPopupContent(
- `Updating: <b>${selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim()}</b>`,
- selected_entries[i].querySelector(".image").style
- .backgroundImage,
- i + 1,
- selected_entries.length
- )
- );
- while (true) {
- if (all_processed_custom_lists) {
- values.customLists = all_processed_custom_lists[i];
- }
- if (all_processed_advanced_scores) {
- values.advancedScores = all_processed_advanced_scores[i];
- }
- const update_response = await updateEntry(ids[i], values);
- if (update_response.errors) {
- const entry_title = selected_entries[i]
- .querySelector(".title > a")
- .innerText.trim();
- const error_message = `An error occurred while updating <b>${entry_title}</b>. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- }
- closePopup();
- return true;
- }
-
- // Don't batch update if not required
- if (
- status_enabled_checkbox.checked ||
- score_enabled_checkbox.checked ||
- (advanced_scores.length > 0 &&
- advanced_scores_enabled_checkboxes.every((e) => e.checked)) ||
- (is_list_anime &&
- (progress_inputs.episode_enabled_checkbox.checked ||
- progress_inputs.rewatches_enabled_checkbox.checked)) ||
- (!is_list_anime &&
- (progress_inputs.chapter_enabled_checkbox.checked ||
- progress_inputs.volume_enabled_checkbox.checked ||
- progress_inputs.rereads_enabled_checkbox.checked)) ||
- start_date_enabled_checkbox.checked ||
- finish_date_enabled_checkbox.checked ||
- notes_enabled_checkbox.checked ||
- (custom_lists.length > 0 &&
- !hide_from_status_list_checkbox.indeterminate) ||
- !private_checkbox.indeterminate
- ) {
- changePopupContent(
- "Updating all the entries at once. Not possible to cancel."
- );
- while (true) {
- const batch_update_response = await batchUpdateEntries(
- ids,
- values_to_be_changed
- );
- if (batch_update_response.errors) {
- const error_message = `An error occurred while batch updating. Please look at the console for more information. Do you want to retry or cancel the request?`;
- if (await createErrorPopup(error_message)) {
- closePopup();
- return false;
- }
- } else {
- break;
- }
- }
- }
-
- closePopup();
- return true;
- })();
-
- if (success) {
- const finished_popup_button = createConfirmPopup(
- "Done!",
- "The request has finished. Do you want to refresh?"
- );
- finished_popup_button.onclick = () => window.location.reload();
- }
- };
- };
-
- new MutationObserver(() => {
- const selected_entries = document.querySelectorAll(
- ".rtonne-anilist-multiselect-selected"
- ).length;
- currently_selected_label.innerHTML = `You have <b><u>${selected_entries}</u></b> entr${
- selected_entries > 1 ? "ies" : "y"
- } selected.`;
- if (selected_entries > 0) {
- form.style.display = "flex";
- help.style.display = "block";
- } else {
- form.style.display = "none";
- help.style.display = "none";
- }
- }).observe(document.querySelector(".lists"), {
- childList: true,
- subtree: true,
- attributeFilter: ["class"],
- });
- }