Civitai Text Downloader Mod

Click Download button will download the file and save the JSON and images at the same time. Also add a button to download JSON and images under details.

  1. // ==UserScript==
  2. // @name Civitai Text Downloader Mod
  3. // @name:ja Civitai Text Downloader Mod
  4. // @namespace http://tampermonkey.net/
  5. // @version 5.3.5
  6. // @description Click Download button will download the file and save the JSON and images at the same time. Also add a button to download JSON and images under details.
  7. // @description:ja Downloadボタンをクリックするとファイルのダウンロードと同時にJSONと画像が保存されます。また、Detailsの下にJSONと画像をダウンロードするボタンを追加します。
  8. // @author Takenoko3333
  9. // @match https://civitai.com/*
  10. // @icon https://civitai.com/favicon.ico
  11. // @grant GM.addStyle
  12. // @grant GM.xmlHttpRequest
  13. // @connect civitai.com
  14. // @license BSD
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19. GM.addStyle(`.ctd-button:not([data-disabled]) {color: #ffff00;}`);
  20.  
  21. let file_id = null;
  22. let category = "";
  23. if (typeof MutationObserver !== 'undefined') {
  24. let lastUrl = location.href;
  25. let observerTriggered = false;
  26. const observer = new MutationObserver((mutations) => {
  27. if (lastUrl !== location.href) {
  28. lastUrl = location.href;
  29. observerTriggered = true;
  30. const interval = setInterval(function() {
  31. downloadFiles();
  32. if (file_id || category != "models" ) {
  33. clearInterval(interval);
  34. }
  35. }, 200);
  36. }
  37. });
  38. const config = { subtree: true, childList: true };
  39. observer.observe(document, config);
  40. // URL change not detected (initial access)
  41. setTimeout(() => {
  42. if (!observerTriggered) {
  43. if (isFirefox()) {
  44. runDownloadFilesForFirefox();
  45. } else {
  46. downloadFiles();
  47. }
  48. }
  49. }, 0);
  50. } else {
  51. // MutationObserver is not supported
  52. setInterval(function(){
  53. downloadFiles();
  54. }, 500);
  55. }
  56.  
  57. function isFirefox() {
  58. const isFirefoxBrowser = typeof InstallTrigger !== 'undefined';
  59. return isFirefoxBrowser;
  60. }
  61.  
  62. function runDownloadFilesForFirefox() {
  63. let counter = 0;
  64. const interval = setInterval(function() {
  65. downloadFiles();
  66. counter++;
  67. if (category != "models" || counter >= 3) {
  68. clearInterval(interval);
  69. }
  70. }, 500);
  71. }
  72.  
  73. function createButton() {
  74. const newElement = document.createElement('a');
  75. newElement.className = 'mantine-UnstyledButton-root mantine-Button-root mantine-4fe1an json-image-download-button';
  76. newElement.type = 'button';
  77. newElement.setAttribute('data-button', 'true');
  78. newElement.style.marginTop = '8px';
  79.  
  80. const innerDiv = document.createElement('div');
  81. innerDiv.className = 'mantine-3xbgk5 mantine-Button-inner';
  82.  
  83. const innerSpan = document.createElement('span');
  84. innerSpan.className = 'mantine-qo1k2 mantine-Button-label';
  85. innerSpan.textContent = 'JSON and Image Download';
  86.  
  87. innerDiv.appendChild(innerSpan);
  88. newElement.appendChild(innerDiv);
  89.  
  90. const tableElement = document.querySelector('table');
  91. let currentElement = tableElement;
  92. while (currentElement && !currentElement.classList.contains('mantine-Accordion-item')) {
  93. currentElement = currentElement.parentElement;
  94. }
  95. if (currentElement) {
  96. currentElement.insertAdjacentElement('afterend', newElement);
  97. }
  98. }
  99.  
  100. function downloadFiles() {
  101. file_id = null
  102. category = location.pathname.split("/")[1];
  103. if (category != "models") return;
  104.  
  105. const codeElements = document.querySelectorAll('table code');
  106. codeElements.forEach((element, index) => {
  107. if (element.textContent === '@') {
  108. if (codeElements[index + 1]) {
  109. file_id = codeElements[index + 1].textContent;
  110. }
  111. }
  112. });
  113.  
  114. const jsonImageDownloadButton = document.querySelector('.json-image-download-button');
  115. if (file_id && !jsonImageDownloadButton) createButton();
  116.  
  117. document.querySelectorAll('main a[type^="button"]').forEach(button => {
  118. if(!button.classList.contains("ctd-done")){
  119. button.addEventListener("click", function(){
  120. const modelName = document.querySelector('main h1').textContent;
  121. const spanElements = document.querySelectorAll('table span');
  122. let strength = null;
  123. let _id = location.pathname.split("/")[2];
  124.  
  125. spanElements.forEach((element, index) => {
  126. if (/^Strength:/.test(element.textContent)) {
  127. strength = element.textContent.split(":")[1].trim();
  128. }
  129. });
  130.  
  131. GM.xmlHttpRequest({
  132. method: "GET",
  133. url: "https://civitai.com/api/v1/models/" + _id,
  134. onload: function(response) {
  135. let j = JSON.parse(response.responseText);
  136. let file = j.modelVersions.find(x => x.id == file_id);
  137. let link = document.createElement('a');
  138. let text = {
  139. "description": "",
  140. "model name": modelName,
  141. "model url": document.URL,
  142. "base model": file.baseModel,
  143. "sd version": "Unknown",
  144. "activation text": "",
  145. "preferred weight": 0,
  146. "notes": document.URL + "\nModel name: " + modelName + "\nBase model: " + file.baseModel
  147. };
  148.  
  149. if (/^SD 1/.test(file.baseModel)) {
  150. text["sd version"] = "SD1";
  151. } else if (/^SD 2/.test(file.baseModel)) {
  152. text["sd version"] = "SD2";
  153. } else if (/^SDXL/.test(file.baseModel) || /^Pony/.test(file.baseModel)) {
  154. text["sd version"] = "SDXL";
  155. } else if (/^SD 3/.test(file.baseModel)) {
  156. text["sd version"] = "SD3";
  157. }
  158.  
  159. if (j.description && j.description.textContent) {
  160. text.description = j.description.textContent;
  161. }
  162. if (file.trainedWords) {
  163. text["activation text"] = file.trainedWords.join(" ");
  164. }
  165. if (strength) {
  166. text["preferred weight"] = strength;
  167. }
  168. text = [JSON.stringify(text)];
  169. link.href = window.URL.createObjectURL(new Blob(text));
  170. let filename = file.files.find(x => x).name || file_id + ".json";
  171. filename = filename.replace(/\.[a-z]*$/, ".json")
  172. link.download = filename;
  173. link.click();
  174.  
  175. let image = file.images.find(x =>x.type === 'image');
  176. if (image) {
  177. GM.xmlHttpRequest({
  178. method: "GET",
  179. url: image.url,
  180. responseType: "blob",
  181. onload: function (response) {
  182. let dlLink = document.createElement("a");
  183. const dataUrl = URL.createObjectURL(response.response);
  184. dlLink.href = dataUrl;
  185. let suffix = "." + image.url.slice(image.url.lastIndexOf('.') + 1).toLowerCase();
  186. if (suffix === ".jpeg") {
  187. suffix = ".jpg";
  188. }
  189. filename = filename.replace(".json", suffix)
  190. dlLink.download = filename;
  191. document.body.insertAdjacentElement("beforeEnd", dlLink);
  192. dlLink.click();
  193. dlLink.remove();
  194. setTimeout(function () {
  195. window.URL.revokeObjectURL(dataUrl);
  196. }, 1000);
  197. }
  198. });
  199. }
  200. }
  201. });
  202. });
  203. if (file_id && !jsonImageDownloadButton) button.classList.add("ctd-button");
  204. button.classList.add("ctd-done");
  205. }
  206. });
  207. }
  208. })();