您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fetch available tag information in tag search with one click
// ==UserScript== // @name AO3: [Wrangling] Fetch All Tag Info // @description Fetch available tag information in tag search with one click // @version 0.1 // @author endofthyme // @namespace http://tampermonkey.net/ // @license GPL-3.0 <https://www.gnu.org/licenses/gpl.html> // @match *://*.archiveofourown.org/tags/search?* // @grant none // ==/UserScript== //IMPORTANT!! Requires the show results in a table script!! (Totally recommended, it's an awesome script!) //https://gf.qytechs.cn/en/scripts/448079-ao3-wrangling-search-term-highlighting-and-table // WARNING: Setting this to true will result in a second page load per tag. var CHECK_UNIQUE_USERS = false; const RATE_LIMITED = "Rate limited. Sorry :(" const UNEXPECTED_ERROR = "Unexpected error, check the console" const workscolumnlabel = CHECK_UNIQUE_USERS ? "Users" : "Works" const columnlist = ["synnedto", "iscanonical", "comments", "works", "metatags", "subtags"] const columninnertextlist = ["Synned to", "Canon?", "Comments", workscolumnlabel, "Metatags", "Subtags"] const checkboxlabels = ["Syns ", "Canonical ", "Comments ", workscolumnlabel + " ", "Metatags ", "Subtags "] // other idea: is wrangled? //So things like getElementsByTagName get a nodelist, not an array //This lets us convert the nodelists into an actual array so we can use array functions on it //See https://stackoverflow.com/questions/5145032/whats-the-use-of-array-prototype-slice-callarray-0 const array = a => Array.prototype.slice.call(a, 0) //Returns a tag's fandoms //Does this by peeking at all the "fandoms to remove" on the tag's edit form and putting them into a list function check_fandoms(xhr) { const fandoms = [] const checks = array(xhr.responseXML.documentElement.querySelectorAll("label > input[name='tag[associations_to_remove][]']")).filter(foo => foo.id.indexOf("parent_Fandom_associations_to_remove") != -1); for (const check of checks) { const name = check.parentElement.nextElementSibling.innerText fandoms.push(name) } return fandoms.length == 0 ? "Unwrangled" : fandoms.join(", "); } function check_using_checkboxes(xhr, label_prefix) { const output = [] const checks = array(xhr.responseXML.documentElement.querySelectorAll("label > input[name='tag[associations_to_remove][]']")).filter(foo => foo.id.indexOf(label_prefix + "_associations_to_remove") != -1); for (const check of checks) { const name = check.parentElement.nextElementSibling.innerText output.push(name) } return output.join(", "); } //Returns what a tag is synned to, or an empty string if its unsynned function check_synnedto(xhr) { return xhr.responseXML.documentElement.querySelector("#tag_syn_string")?.value || "" } //Checks if tag is canonized function check_if_canonical(xhr) { return xhr.responseXML.documentElement.querySelector("#tag_canonical")?.checked ? '✔' : ""; } //Returns the comments function check_comments(xhr) { const commenthtml = xhr.responseXML.documentElement.querySelector("p.navigation > a")?.innerHTML if (commenthtml != null) { const comment_count = commenthtml.match(/^\d+/) const comment_date = commenthtml.match(/\d{4}-\d{2}-\d{2}/) return comment_count[0] + (comment_date ? " (last: " + comment_date[0] + ")" : "") } return "" } //Returns the work count function check_works(xhr) { return xhr.responseXML.documentElement.querySelector("#dashboard ul.navigation.actions > li a[href*='works']")?.innerHTML.match(/\d+/) || "" } //Returns the unique user count (from the tag landing page instead of the edit page) function check_unique_users(xhr) { const works = array(xhr.responseXML.documentElement.querySelectorAll("div.work div.header h4")) var users = new Set(); var user_count = 0; for (const work of works) { var found_a_user = false; var found_repeat_user_or_orphaned = false; var new_user_set = new Set(users); const authors = array(work.querySelectorAll("a[rel$='author']")) for (const author of authors) { var parsed_user = author.innerHTML; var check_pseud = parsed_user.match(/[^(]*\(([^)]*)\)/) // do not count pseuds separately if (check_pseud) { parsed_user = check_pseud[1] } if (parsed_user) { found_a_user = true; if (parsed_user == "orphan_account" || users.has(parsed_user)) { found_repeat_user_or_orphaned = true; } else { new_user_set.add(parsed_user); } } } if (found_a_user && !found_repeat_user_or_orphaned) { user_count++; // only add users to list if the work was counted as a unique use for (const x of new_user_set) { users.add(x); } } } const has_multiple_pages = xhr.responseXML.documentElement.querySelectorAll("div.work h4.landmark").length > 0; return user_count + (has_multiple_pages ? "+" : "") } function gather_data(url, a, result, tdlist, fandom) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function xhr_onreadystatechange() { if (xhr.readyState == xhr.DONE ) { if (xhr.status == 200) { const fandoms = check_using_checkboxes(xhr, "parent_Fandom") if (fandoms == "") { result.innerText = "Unwrangled" } else if (fandom == fandoms) { result.innerText = "-" } else { result.innerText = fandoms } tdlist[0].innerText = check_synnedto(xhr) tdlist[1].innerText = check_if_canonical(xhr) tdlist[2].innerText = check_comments(xhr) tdlist[4].innerText = check_using_checkboxes(xhr, "parent_MetaTag") tdlist[5].innerText = check_using_checkboxes(xhr, "child_SubTag") if (!CHECK_UNIQUE_USERS) { tdlist[3].innerText = check_works(xhr) } else if (tdlist[1].innerText != '✔') { const xhr2 = new XMLHttpRequest(); xhr2.onreadystatechange = function xhr2_onreadystatechange() { if (xhr2.readyState == xhr2.DONE ) { if (xhr2.status == 200) { tdlist[3].innerText = check_unique_users(xhr2) } else if (xhr2.status == 429) { result.innerText = RATE_LIMITED } else { result.innerText = UNEXPECTED_ERROR console.log(xhr2) } } } xhr2.open("GET", url) xhr2.responseType = "document" xhr2.send() } } else if (xhr.status == 429) { result.innerText = RATE_LIMITED } else { result.innerText = UNEXPECTED_ERROR console.log(xhr) } } } xhr.open("GET", url + "/edit") xhr.responseType = "document" xhr.send() } (function($) { 'use strict'; var page_url = window.location.href; const button = document.createElement("button") const buttondiv = document.createElement("div") var checkboxlist = [] for (let i = 0; i <= columnlist.length; i++) { const checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.checked = true; checkboxlist.push(checkbox); } const checkboxdiv = document.createElement("div") checkboxdiv.style.marginTop = "10px" checkboxdiv.style.fontSize = "0.875rem" const fetch_data = function fetch_data(e) { e.preventDefault() //requires show results in table script to be enabled //https://gf.qytechs.cn/en/scripts/448079-ao3-wrangling-search-term-highlighting-and-table const search_results = document.getElementById('resulttable').getElementsByClassName("tag") const fandom_input = document.getElementById('new_tag_search').getElementsByClassName("added") var fandom = "" if (fandom_input.length == 1) fandom = fandom_input[0].innerText.replace(" ×","") //These columns are not present by default document.querySelector("#resulttable .resultcheck").style.display = "table-cell" document.getElementById('resulttable').getElementsByClassName("resultcheck")[0].appendChild(document.createElement("span")); var columnindex = 0; for (const columnname of columnlist) { const column = document.createElement("th"); column.innerText = columninnertextlist[columnindex]; column.classList.add(columnname); column.appendChild(document.createElement("span")); document.getElementById('resulttable').getElementsByClassName("resultcheck")[0].parentElement.appendChild(column); columnindex += 1; } // **** start of table sorting logic (thanks owlwinter!!) const headers = array(document.getElementById('resulttable').querySelectorAll('thead th')); console.log(headers) for (let i = 0; i <= 4 + columnlist.length; i++) { headers[i].addEventListener('click', function (index) { sortColumn(i); }); } // Track sort directions //If a row is clicked twice in a row, it will do a reverse sort //But if clicked a third time, it will switch back to a normal sort //And so on and so forth const directions = Array.from(headers).map(function (header) { return ''; }); //Special case - if you click sort by uses, we want to start with the highest uses at the top directions[1] = 'desc'; const sortColumn = function (index) { // Get the current direction const direction = directions[index] || 'asc'; // add a sorting direction triangle indicator $(headers[index]).find('span').html((direction === 'asc') ? '▴' : ' ▾'); // A factor based on the direction const multiplier = (direction === 'asc') ? 1 : -1; const table = document.getElementById('resulttable'); const tableBody = table.querySelector('tbody'); const rows = tableBody.querySelectorAll('tr'); // Clone the rows const newRows = Array.from(rows); // Sort rows by the content of cells newRows.sort(function (rowA, rowB) { let cellA; let cellB; // Get the content of cells if (index == 0) { cellA = rowA.querySelectorAll('th')[index].textContent.toLowerCase(); cellB = rowB.querySelectorAll('th')[index].textContent.toLowerCase(); } else { cellA = rowA.querySelectorAll('td')[index-1].textContent.toLowerCase(); cellB = rowB.querySelectorAll('td')[index-1].textContent.toLowerCase(); } if (cellA.match(/^\d+$/) && cellB.match(/^\d+$/)) { //javascript sure is something alright ! //...without this, it will happily tell you that 8938 < 9 cellA = parseFloat(cellA); cellB = parseFloat(cellB); } switch (true) { case cellA > cellB: return 1 * multiplier; case cellA < cellB: return -1 * multiplier; case cellA === cellB: return 0; } }); // Remove old rows [].forEach.call(rows, function (row) { tableBody.removeChild(row); }); // Append new row newRows.forEach(function (newRow) { tableBody.appendChild(newRow); }); // Reverse the direction directions[index] = direction == 'asc' ? 'desc' : 'asc'; } // **** end of table sorting logic // find each item in the tag search results list for (const a of search_results) { const span = a.parentElement.parentElement.getElementsByClassName("resultcheck")[0]; a.parentElement.parentElement.getElementsByClassName("resultcheck")[0].style.display = "table-cell" span.innerHTML = "" // this is the span that will hold the result when the request finishes const loading = document.createElement("span"); //Before our request finishes, shows a loading text so user knows something is happening loading.innerText = "Loading..." loading.style.fontStyle = "italic" span.appendChild(loading); var tdlist = [] for (const item of columnlist) { const td = document.createElement("td") td.classList.add(item) a.parentElement.parentElement.appendChild(td) tdlist.push(td) } // trigger xhr (asynchronous) gather_data(a.href, a, loading, tdlist, fandom); } //Renames the header of the table document.getElementById('resulttable').querySelector('thead th.resultcheck').innerHTML = "Fandom"; document.getElementById('resulttable').querySelector('thead th.resultcheck').appendChild(document.createElement("span")) //Removes button after it's clicked button.parentElement.parentElement.removeChild(buttondiv) } const show_check_column = function show_check_column(e) { for (var column of document.getElementsByClassName(this)) { if (e.currentTarget.checked) { column.style.display = "table-cell"; } else { column.style.display = "none"; } } } // Add the load fandom button - since this can get someone rate limited, // we definitely don't want to have it happen automatically button.innerText = "Fetch data" button.addEventListener("click", fetch_data) button.style.display = "inline" button.style.fontSize = "0.627rem" button.style.marginTop = "10px" buttondiv.append(button) checkboxlist[0].addEventListener("change", show_check_column.bind("resultcheck")) checkboxdiv.append("Show columns: Fandoms ") checkboxdiv.append(checkboxlist[0]) const buffer = String.fromCharCode(160, 160, 160, 160) for (let i = 0; i < columnlist.length; i++) { checkboxlist[i + 1].addEventListener("change", show_check_column.bind(columnlist[i])) checkboxdiv.append(buffer + checkboxlabels[i]) checkboxdiv.append(checkboxlist[i + 1]) } document.querySelector("#main > h3").append(buttondiv) document.querySelector("#main > h3").append(checkboxdiv) // removes the color background color of the not synned tags are when the user's cursor hovers over them // trust me, keeping the same magenta but with the blue text was atrocious // comment out the next three lines out at your own risk // ......you really don't want to // .........don't say i didn't warn you...., const style = document.createElement("style") style.innerHTML = ".not_synned:hover { background-color: white !important; }" document.head.appendChild(style) })(jQuery);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址