Thing Remix Attribution Maker

Format the license information from a thing page

  1. // ==UserScript==
  2. // @name Thing Remix Attribution Maker
  3. // @namespace http://poikilos.org/
  4. // @version 4.0.0
  5. // @description Format the license information from a thing page
  6. // @author Poikilos (Jake Gustafson)
  7. // @match https://www.thingiverse.com/thing:*
  8. // @grant none
  9. // @run-at document-end
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. // now its @match again :eyeroll: -- eslint says it may be obsolete in Manifest v3 in early 2023
  14. // formerly @include https://www.thingiverse.com/thing:*
  15. // formerly @match https://www.thingiverse.com/thing:*
  16. var verbose = true;
  17. var checkTimer = null;
  18. var licenseClauseImgPrefix = "License__img";
  19. var licenseAnchorPrefix = "License__link"; // INFO: License__link* could be author OR license link; author also has License__creator* class.
  20. var titlePrefix = "ThingTitle__modelName";
  21. var headingCreatedPrefix = "ThingTitle__createdBy";
  22. var madeDivClassName = "ThingTitle__madeBy"; // doneDivPrefixes' container[0]'s class (a.k.a. .className)
  23. var doneDivPrefixes = [titlePrefix, headingCreatedPrefix];
  24. var clausesContainerPrefix = "License__ccLicense";
  25. var doneDivPrefixesMain = [clausesContainerPrefix];
  26. var urlSmallNames = {
  27. "/by/1.0": "CC BY 1.0",
  28. "/by/2.0": "CC BY 2.0",
  29. "/by/2.5": "CC BY 2.5",
  30. "/by/3.0": "CC BY 3.0",
  31. "/by/4.0": "CC BY 4.0",
  32. "/by-sa/1.0": "CC BY-SA 1.0",
  33. "/by-sa/2.0": "CC BY-SA 2.0",
  34. "/by-sa/2.5": "CC BY-SA 2.5",
  35. "/by-sa/3.0": "CC BY-SA 3.0",
  36. "/by-sa/4.0": "CC BY-SA 4.0",
  37. "/by-nc/1.0": "CC BY-NC 1.0",
  38. "/by-nc/2.0": "CC BY-NC 2.0",
  39. "/by-nc/2.5": "CC BY-NC 2.5",
  40. "/by-nc/3.0": "CC BY-NC 3.0",
  41. "/by-nc/4.0": "CC BY-NC 4.0",
  42. "/by-nc-sa/1.0": "CC BY-NC-SA 1.0",
  43. "/by-nc-sa/2.0": "CC BY-NC-SA 2.0",
  44. "/by-nc-sa/2.5": "CC BY-NC-SA 2.5",
  45. "/by-nc-sa/3.0": "CC BY-NC-SA 3.0",
  46. "/by-nc-sa/4.0": "CC BY-NC-SA 4.0",
  47. "/by-nd/1.0": "CC BY-ND 1.0",
  48. "/by-nd/2.0": "CC BY-ND 2.0",
  49. "/by-nd/2.5": "CC BY-ND 2.5",
  50. "/by-nd/3.0": "CC BY-ND 3.0",
  51. "/by-nd/4.0": "CC BY-ND 4.0",
  52. "/by-nd-nc/1.0": "CC BY-ND-NC 1.0",
  53. "/by-nc-nd/2.0": "CC BY-NC-ND 2.0",
  54. "/by-nc-nd/2.5": "CC BY-NC-ND 2.5",
  55. "/by-nc-nd/3.0": "CC BY-NC-ND 3.0",
  56. "/by-nc-nd/4.0": "CC BY-NC-ND 4.0",
  57. "creativecommons.org/share-your-work/public-domain/cc0": "CC0",
  58. "creativecommons.org/publicdomain/zero/1.0": "CC0 1.0",
  59. };
  60. var bigNames = {
  61. "CC BY 1.0": "Creative Commons Attribution 1.0 Generic",
  62. "CC BY 2.0": "Creative Commons Attribution 2.0 Generic",
  63. "CC BY 2.5": "Creative Commons Attribution 2.5 Generic",
  64. "CC BY 3.0": "Creative Commons Attribution 3.0 Unported",
  65. "CC BY 4.0": "Creative Commons Attribution 4.0 International",
  66. "CC BY-SA 1.0": "Creative Commons Attribution-ShareAlike 1.0 Generic",
  67. "CC BY-SA 2.0": "Creative Commons Attribution-ShareAlike 2.0 Generic",
  68. "CC BY-SA 2.5": "Creative Commons Attribution-ShareAlike 2.5 Generic",
  69. "CC BY-SA 3.0": "Creative Commons Attribution-ShareAlike 3.0 Unported",
  70. "CC BY-SA 4.0": "Creative Commons Attribution-ShareAlike 4.0 International",
  71. "CC BY-NC 1.0": "Creative Commons Attribution-NonCommercial 1.0 Generic",
  72. "CC BY-NC 2.0": "Creative Commons Attribution-NonCommercial 2.0 Generic",
  73. "CC BY-NC 2.5": "Creative Commons Attribution-NonCommercial 2.5 Generic",
  74. "CC BY-NC 3.0": "Creative Commons Attribution-NonCommercial 3.0 Unported",
  75. "CC BY-NC 4.0": "Creative Commons Attribution-NonCommercial 4.0 International",
  76. "CC BY-NC-SA 1.0": "Creative Commons Attribution-NonCommercial-ShareAlike 1.0 Generic",
  77. "CC BY-NC-SA 2.0": "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Generic",
  78. "CC BY-NC-SA 2.5": "Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Generic",
  79. "CC BY-NC-SA 3.0": "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
  80. "CC BY-NC-SA 4.0": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International",
  81. "CC BY-ND 1.0": "Creative Commons Attribution-NoDerivs 1.0 Generic",
  82. "CC BY-ND 2.0": "Creative Commons Attribution-NoDerivs 2.0 Generic",
  83. "CC BY-ND 2.5": "Creative Commons Attribution-NoDerivs 2.5 Generic",
  84. "CC BY-ND 3.0": "Creative Commons Attribution-NoDerivs 3.0 Unported",
  85. "CC BY-ND 4.0": "Creative Commons Attribution-NoDerivatives 4.0 International",
  86. "CC BY-ND-NC 1.0": "Creative Commons Attribution-NoDerivs-NonCommercial 1.0 Generic",
  87. "CC BY-NC-ND 2.0": "Creative Commons Attribution-NonCommercial-NoDerivs 2.0 Generic",
  88. "CC BY-NC-ND 2.5": "Creative Commons Attribution-NonCommercial-NoDerivs 2.5 Generic",
  89. "CC BY-NC-ND 3.0": "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
  90. "CC BY-NC-ND 4.0": "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International",
  91. "CC0": "No Rights Reserved",
  92. "CC0 1.0": "Creative Commons CC0 1.0 Universal",
  93. };
  94. function getElementsWhereClassStartsWith(str) {
  95. if (verbose) {
  96. console.log("");
  97. console.log("getElementsWhereClassStartsWith(\""+str+"\")...");
  98. }
  99. var els = [];
  100. var all = document.getElementsByTagName("*");
  101. for (var i=0, max=all.length; i < max; i++) {
  102. var el = all[i];
  103. if (el.className && !el.className.startsWith) {
  104. // Why This started happening (className.startsWith not existing) when className is true is unclear.
  105. if (el.class) {
  106. console.log("Warning: no el.className.startsWith for class="+el.class+" + el.className="+el.className);
  107. }
  108. else {
  109. console.log("Warning: no el.className.startsWith for el.className="+el.className);
  110. }
  111. }
  112.  
  113. if (el.class && el.class.startsWith(str)) {
  114. els.push(el);
  115. }
  116. else if (el.className && el.className.startsWith && el.className.startsWith(str)) {
  117. els.push(el);
  118. }
  119.  
  120. }
  121. if (verbose) {
  122. console.log("- FOUND " + els.length);
  123. }
  124. return els;
  125. }
  126.  
  127. function getElementsWhere(tagName, attributeName, value) {
  128. if (verbose) {
  129. console.log("");
  130. console.log("getSubElementsWhere(\""+tagName+"\", \""+attributeName+"\", \""+value+"\")...");
  131. }
  132. var els = [];
  133. var all = document.getElementsByTagName(tagName);
  134. for (var i=0, max=all.length; i < max; i++) {
  135. var el = all[i];
  136. if (el.getAttribute(attributeName) == value) {
  137. els.push(el);
  138. }
  139. }
  140. if (verbose) {
  141. console.log("- FOUND " + els.length);
  142. }
  143. return els;
  144. }
  145. function getDivsWhereClassStartsWith(str) {
  146. if (verbose) {
  147. console.log("");
  148. console.log("FIND getDivsWhereClassStartsWith(\"" + str + "\")...");
  149. }
  150. var els = [];
  151. var all = document.getElementsByTagName("div");
  152. for (var i=0, max=all.length; i < max; i++) {
  153. var el = all[i];
  154. if (el.className.startsWith(str)) {
  155. els.push(el);
  156. // console.log("- FOUND (" + els.length + ")");
  157. }
  158. else {
  159. // console.log("- " + el.className + " does not start with it.");
  160. }
  161. }
  162. if (verbose) {
  163. // console.log("Div count: " + all.length);
  164. console.log("- FOUND " + els.length);
  165. }
  166. return els;
  167. }
  168. function getWhereClassStartsWithIn(el, str) {
  169. if (el === undefined) {
  170. console.log("[getWhereClassStartsWithIn] Error: el is undefined.");
  171. return [];
  172. }
  173. if (verbose) {
  174. console.log("");
  175. console.log("DETECT getWhereClassStartsWithIn(el, \""+str+"\")...");
  176. // console.log(" el: " + JSON.stringify(el)); // DON'T do (could be circular)
  177. console.log(" el.className: "+el.className);
  178. console.log(" el.childNodes.length:"+el.childNodes.length+"...");
  179. }
  180. var els = [];
  181. var all = el.childNodes;
  182. for (var i=0, max=all.length; i < max; i++) {
  183. var thisEl = all[i];
  184. if ((thisEl.className != undefined) && thisEl.className.startsWith(str)) {
  185. els.push(thisEl);
  186. }
  187. else {
  188. // console.log("- "+el.className+" does not start with "+str+".");
  189. }
  190. }
  191. if (verbose) {
  192. console.log("- FOUND " + els.length + " " + str);
  193. // console.log("- done (div count: " + all.length + ")");
  194. }
  195. return els;
  196. }
  197. function hasAllDivPrefixes(prefixes) {
  198. var found = 0;
  199. for (var i=0, max=prefixes.length; i < max; i++) {
  200. if (getDivsWhereClassStartsWith(prefixes[i]).length > 0) {
  201. found++;
  202. }
  203. }
  204. return found >= prefixes.length;
  205. }
  206. function hasAllClasses(classNames) {
  207. var found = 0;
  208. for (var i=0, max=classNames.length; i < max; i++) {
  209. if (document.getElementsByClassName(classNames[i]).length > 0) {
  210. found++;
  211. }
  212. else {
  213. if (verbose) {
  214. console.error("The className " + classNames[i] + " was not found.")
  215. }
  216. }
  217. }
  218. return found >= classNames.length;
  219. }
  220. function elementHasAllPrefixes(el, prefixes) {
  221. var found = 0;
  222. for (var i=0, max=prefixes.length; i < max; i++) {
  223. if (getWhereClassStartsWithIn(el, prefixes[i]).length > 0) {
  224. found++;
  225. }
  226. }
  227. return found >= prefixes.length;
  228. }
  229. function getImgsWhereClassStartsWith(str) {
  230. var els = [];
  231. var all = document.images; // document.getElementsByTagName("img");
  232. for (var i=0, max=all.length; i < max; i++) {
  233. var el = all[i];
  234. if (el.className.startsWith(str)) {
  235. els.push(el);
  236. }
  237. }
  238. return els;
  239. }
  240. function getAnchorsWhereClassStartsWith(str) {
  241. if (verbose) {
  242. console.log("getAnchorsWhereClassStartsWith(\""+str+"\")...")
  243. }
  244. var els = [];
  245. var all = document.getElementsByTagName("a");
  246. for (var i=0, max=all.length; i < max; i++) {
  247. var el = all[i];
  248. if (el.className.startsWith(str)) {
  249. els.push(el);
  250. }
  251. }
  252. if (verbose) {
  253. console.log("- FOUND " + els.length);
  254. // console.log("- done (div count: " + all.length + ")");
  255. }
  256. return els;
  257. }
  258. function getAnchorsWhereHrefContains(str) {
  259. // Example: str=field_art_tags_tid= finds <a href="/art-search-advanced?field_art_tags_tid=grass">...
  260. if (verbose) {
  261. console.log("getAnchorsWhereHrefContains(\""+str+"\")...")
  262. }
  263. var els = [];
  264. var all = document.getElementsByTagName("a");
  265. for (var i=0, max=all.length; i < max; i++) {
  266. var el = all[i];
  267. if (el.href.includes(str)) {
  268. els.push(el);
  269. }
  270. }
  271. if (verbose) {
  272. console.log("- FOUND " + els.length);
  273. // console.log("- done (div count: " + all.length + ")");
  274. }
  275. return els;
  276. }
  277.  
  278. function elementAToMarkdown(element) {
  279. var ret = null;
  280. if (element.href) {
  281. ret = "[" + element.textContent + "](" + element.href + ")";
  282. }
  283. else {
  284. if (verbose) {
  285. console.warn("- elementAToMarkdown " + element.textContent + " href is blank in elementAToMarkdown: \"" + element.href + "\"")
  286. }
  287. ret = element.textContent;
  288. }
  289. return ret;
  290. }
  291.  
  292. function getMarkdown(info) {
  293. if (verbose) {
  294. console.log("");
  295. console.log("getMarkdown...");
  296. }
  297. var outputStr = "";
  298. outputStr = "## License";
  299. if (info.license) {
  300. outputStr += "\n- ";
  301. if (info.licenseHref) {
  302. outputStr += "[" + info.license + "](" + info.licenseHref + ")";
  303. }
  304. else {
  305. // console.log("* skipping long name detection since a license was detected: \""+info.license+"\"");
  306. outputStr += info.license;
  307. }
  308. if (info.shortLicense) {
  309. outputStr += "\n (" + info.shortLicense + ")";
  310. }
  311. }
  312. else {
  313. outputStr += "\n- LICENSE: [insert license name (&URL unless in each content ZIP) of original thing here]";
  314. }
  315.  
  316. if (info.author) {
  317. outputStr += "\n- by " + info.author + " and <insert remixer's name here>";
  318. }
  319. if (info.title) {
  320. outputStr += "\n- based on";
  321. if (info.titleHref) {
  322. outputStr += " [" + info.title + "](" + info.titleHref + ")";
  323. }
  324. else {
  325. outputStr += " " + info.title;
  326. }
  327. if (info.author) {
  328. if (info.authorHref) {
  329. outputStr += " by [" + info.author + "](" + info.authorHref + ")";
  330. }
  331. else {
  332. outputStr += " by " + info.author;
  333. }
  334. }
  335. if (info.year) {
  336. outputStr += " ";
  337. if (info.month) {
  338. outputStr += info.month + " ";
  339. if (info.day) {
  340. outputStr += info.day + ", "
  341. }
  342. }
  343. outputStr += info.year;
  344. }
  345. }
  346.  
  347. return outputStr;
  348. }
  349.  
  350. function populateCorrespondingLicenseFields(info) {
  351. var licenseShortStr = "";
  352. if (info.license) {
  353. var versionIsFound = false;
  354. var licenseLower = info.license.toLowerCase();
  355. if (info.license.startsWith("Creative Commons") || info.license.startsWith("CC")) {
  356. if (info.license.startsWith("CC0 1.0") || info.license.startsWith("Creative Commons 0 1.0") || info.license.startsWith("Creative Commons Zero 1.0")) {
  357. if (!info.licenseHref) {
  358. info.licenseHref = "https://creativecommons.org/publicdomain/zero/1.0/";
  359. }
  360. licenseShortStr = "CCO 1.0";
  361. }
  362. else if ((info.license == "Creative Commons 0") || (info.license == "Creative Commons Zero")) {
  363. licenseShortStr = "CCO";
  364. }
  365. else {
  366. console.log("Looking for license clauses in license name \""+licenseLower+"\"...");
  367. licenseShortStr = "CC ";
  368. if (licenseLower.includes("attribution")) {
  369. licenseShortStr += "BY";
  370. }
  371. if (licenseLower.includes("non-commercial") || licenseLower.includes("noncommercial") || licenseLower.includes("non commercial")) {
  372. licenseShortStr += "-NC";
  373. }
  374. if (licenseLower.includes("no derivatives") || licenseLower.includes("noderivs") || licenseLower.includes("no-derivatives") || licenseLower.includes("noderivatives")) {
  375. licenseShortStr += "-ND";
  376. }
  377. if (licenseLower.includes("sharealike") || licenseLower.includes("share-alike") || licenseLower.includes("share alike") ) {
  378. licenseShortStr += "-SA";
  379. }
  380.  
  381. if (info.license.includes("1.0")) {
  382. licenseShortStr += " 1.0";
  383. versionIsFound = true;
  384. }
  385. else if (info.license.includes("2.0")) {
  386. licenseShortStr += " 2.0";
  387. versionIsFound = true;
  388. }
  389. else if (info.license.includes("3.0")) {
  390. licenseShortStr += " 3.0";
  391. versionIsFound = true;
  392. }
  393. else if (info.license.includes("4.0")) {
  394. licenseShortStr += " 4.0";
  395. versionIsFound = true;
  396. }
  397. else if (exactLicenseVersion !== null) {
  398. licenseShortStr += " " + exactLicenseVersion;
  399. versionIsFound = true;
  400. }
  401. }
  402. }
  403. console.log("licenseShortStr: " + licenseShortStr);
  404. if (!info.licenseHref) {
  405. var parts = licenseShortStr.split(" ");
  406. if (parts.length == 3) {
  407. var partialHref = null;
  408. // such as ["CC", "BY-SA", "3.0"]
  409. if (parts[1] == "BY") {
  410. partialHref = "http://creativecommons.org/licenses/by/";
  411. }
  412. else if (parts[1] == "BY-SA") {
  413. partialHref = "http://creativecommons.org/licenses/by-sa/";
  414. }
  415. else if (parts[1] == "BY-NC-SA") {
  416. partialHref = "http://creativecommons.org/licenses/by-nc-sa/";
  417. }
  418. else if (parts[1] == "BY-NC-ND") {
  419. partialHref = "http://creativecommons.org/licenses/by-nc-nd/";
  420. }
  421. // NOTE: by-nc-nd-sa is NOT a valid license
  422. if (partialHref != null) {
  423. info.licenseHref = partialHref + parts[2] + "/";
  424. }
  425. }
  426. }
  427. }
  428. else if (info.licenseHref) {
  429. if (!licenseShortStr) {
  430. if (verbose) {
  431. console.log("Generating short license name from URL instead of from clauses...");
  432. }
  433. for (var key in urlSmallNames) {
  434. // Check if the property/key is defined in the object itself, not in parent
  435. if (urlSmallNames.hasOwnProperty(key)) {
  436. if (info.licenseHref.includes(key)) {
  437. licenseShortStr = urlSmallNames[key];
  438. if (verbose) {
  439. console.log("- got \""+licenseShortStr+"\" from \""+key+"\"")
  440. }
  441. break;
  442. }
  443. }
  444. }
  445. }
  446. else {
  447. console.log("* using existing licenseShortStr \""+licenseShortStr+"\"");
  448. }
  449.  
  450. if (!licenseShortStr) {
  451. console.warn("Warning: The URL \""+info.licenseHref+"\" is not recognized (No key in Thing Remix Attribution Maker's urlSmallNames is a partial of the URL), so the long license name could not be generated.");
  452. }
  453. else {
  454. info.shortLicense = licenseShortStr;
  455. if (!info.license) {
  456. if (verbose) {
  457. console.log("Generating long license name from URL instead of from clauses...");
  458. }
  459. if (bigNames.hasOwnProperty(licenseShortStr)) {
  460. // ^ The clauses are only in this order for versions above 1.0!
  461. info.license = bigNames[licenseShortStr];
  462. if (verbose) {
  463. console.log("- got \""+info.license+"\"");
  464. }
  465. }
  466. else {
  467. console.warn("Warning: The short license name \""+licenseShortStr+"\" is not recognized (It is not a key in Thing Remix Attribution Maker's bigNames), so the long license name could not be generated.");
  468. }
  469. }
  470. }
  471. }
  472. if (!info.license) {
  473. console.warn("The license abbreviation cannot be generated because no license text was generated (no license elements were detected).");
  474. }
  475. else if (!info.shortLicense) {
  476. console.warn("The license abbreviation cannot be generated for an unknown license: " + info.license);
  477. }
  478. }
  479.  
  480. function setClipboardText(text, callbackBtn) {
  481. var msg = "(ERROR: Your browser API is unknown.)";
  482. if (callbackBtn == null) {
  483. msg = "Error: no callbackBtn";
  484. console.log(msg);
  485. return msg;
  486. }
  487. var okMsg = " &#10003;";
  488. // See https://stackoverflow.com/questions/52177405/clipboard-writetext-doesnt-work-on-mozilla-ie
  489. if (navigator.clipboard != undefined) { // Chrome
  490. navigator.clipboard.writeText(text).then(
  491. function () {
  492. console.log('Async: Copying to clipboard was successful!');
  493. callbackBtn.innerHTML += okMsg;
  494. }, function (err) {
  495. console.error('Async: Could not copy text: ', err);
  496. callbackBtn.innerHTML += '<br/> (ERROR: Accessing the clipboard failed.)';
  497. }
  498. );
  499. msg = null;
  500. }
  501. else if (window.clipboardData) { // Internet Explorer
  502. window.clipboardData.setData("Text", text);
  503. msg = okMsg;
  504. }
  505. if (msg != null) {
  506. callbackBtn.innerHTML += msg;
  507. }
  508. }
  509.  
  510. function getButtonContainer() {
  511. // var pageInfoEs = document.getElementsByClassName("item-page-info");
  512. var pageInfoEs = getElementsWhereClassStartsWith(madeDivClassName);
  513. if (pageInfoEs.length < 1) {
  514. return null;
  515. }
  516. // There should only be one.
  517. return pageInfoEs[0];
  518. }
  519.  
  520. function getInfo() {
  521. 'use strict';
  522. var info = {};
  523. // There should only be one.
  524. // pageInfoE.innerHTML += "<button onclick=\"getRemixLicense()\">Copy Markdown</button>";
  525. // var licenseTextE = document.getElementsByClassName("license-text");
  526. // var licenseTextE = getDivsWhereClassStartsWith(clausesContainerPrefix);
  527. // var pageInfoEs = document.getElementsByClassName("item-page-info");
  528. // var pageInfoEs = getDivsWhereClassStartsWith(madeDivClassName);
  529. // console.log("Checking "+madeDivClassName+"* elements: " + JSON.stringify(pageInfoEs));
  530. var headingParts = getDivsWhereClassStartsWith(titlePrefix);
  531. var headingCreatedParts = getDivsWhereClassStartsWith(headingCreatedPrefix);
  532. if (headingParts.length > 0) {
  533. info.title = headingParts[0].textContent;
  534. }
  535. else {
  536. console.warn("The title is missing. There are no divs with a class starting with " + titlePrefix);
  537. }
  538. var createdStr = null;
  539. if (headingCreatedParts.length > 0) {
  540. createdStr = headingCreatedParts[0].textContent;
  541. }
  542. else {
  543. console.warn("The date is missing. There are no divs with a class starting with " + headingCreatedParts);
  544. }
  545. info.titleHref = window.location.href;
  546. // console.log("info.title: " + info.title);
  547. // console.log("info.titleHref: " + info.titleHref);
  548. console.log("createdStr: " + createdStr);
  549. if (createdStr !== null) {
  550. var createdParts = createdStr.split(" ");
  551. if (createdParts.length >= 3) {
  552. var yI = createdParts.length - 1;
  553. var dI = createdParts.length - 2;
  554. var mI = createdParts.length - 3;
  555. var yStr = createdParts[yI];
  556. var dStr = createdParts[dI];
  557. var mStr = createdParts[mI];
  558. if (dStr.endsWith(",")) {
  559. info.month = mStr;
  560. info.day = dStr.slice(0, -1);
  561. info.year = yStr;
  562. }
  563. else {
  564. console.warn("A date such as MON, D, YYYY was expected at the end of: \""+createdStr+"\"");
  565. }
  566. }
  567. }
  568. var aspects = [];
  569. aspects = getImgsWhereClassStartsWith(licenseClauseImgPrefix);
  570. var ai;
  571. if (aspects.length > 0) {
  572. info.license = "";
  573. }
  574. else {
  575. console.error("The license had zero clauses (img tags with "+licenseClauseImgPrefix+"* class)!")
  576. }
  577. var sep = " - ";
  578. for (ai = 0; ai < aspects.length; ai++) {
  579. var aspectImg = aspects[ai];
  580. if (aspectImg.src == undefined) {
  581. console.error("The license symbol src was undefined.");
  582. }
  583. else if (aspectImg.src == "") {
  584. console.error("The license symbol src was blank.");
  585. }
  586. else if (aspectImg.src.endsWith("cc.svg")) {
  587. info.license += "Creative Commons";
  588. }
  589. else if (aspectImg.src.endsWith("nc.svg")) {
  590. info.license += sep + "Non-Commercial";
  591. }
  592. else if (aspectImg.src.endsWith("nd.svg")) {
  593. info.license += sep + "No Derivatives";
  594. }
  595. else if (aspectImg.src.endsWith("by.svg")) {
  596. info.license += sep + "Attribution";
  597. }
  598. else if (aspectImg.src.endsWith("sa.svg")) {
  599. info.license += sep + "ShareAlike"; // It has a space on ThingiVerse, but that is not correct.
  600. }
  601. else if (aspectImg.src.endsWith("zero.svg")) {
  602. // It is preceded by by.svg on ThingiVerse, but that is not correct.
  603. info.license = "Creative Commons Zero";
  604. }
  605. else {
  606. console.error("The license symbol list has an unknown clause symbol: \"" + aspectImg.src + "\"");
  607. }
  608. }
  609. if (info.license != undefined) {
  610. if (info.license == "") {
  611. console.log("The symbols do not indicate a license (The site layout appears to be broken or changed so the license must be detected from the license URL if possible instead).");
  612. }
  613. else {
  614. console.log("The symbols indicate the following license: " + info.license);
  615. }
  616. }
  617.  
  618.  
  619. var licenseAnchors = getAnchorsWhereClassStartsWith(licenseAnchorPrefix);
  620. var exactLicenseVersion = null;
  621. if (licenseAnchors.length > 0) {
  622. console.log("Checking " + licenseAnchors.length + " license anchors...");
  623. for (var lai=0, max=licenseAnchors.length; lai < max; lai++) {
  624. var licenseA = licenseAnchors[lai];
  625. if (verbose) {
  626. console.log(" checking " + licenseA.className + "...");
  627. // NOTE: .getAttribute("href") gets the raw value, but .href gets the resulting full URL.
  628. console.log(" licenseA.href is a " + typeof licenseA.href);
  629. console.log(" licenseA.href.toString is a " + typeof licenseA.href.toString);
  630. console.log(" licenseA.href.toString().includes is a " + typeof licenseA.href.toString().includes);
  631. }
  632. if (licenseA.href === undefined) {
  633. console.warn("A license a.href is undefined.");
  634. }
  635. // else if (typeof licenseA.href.toString !== 'function') {
  636. // console.warn("A license a.href.toString is not a function.");
  637. // }
  638. else if (typeof licenseA.href.includes !== 'function') {
  639. // NOTE: Firefox 48 removes the "contains" prototype--you must use includes!
  640. // console.warn("A license a.getAttribute(\"href\").includes is not a function.");
  641. console.warn("A license a.href.toString.includes is not a function.");
  642. }
  643. else if (!licenseA.href.includes("thingiverse.com")) {
  644. if (verbose) {
  645. console.log("licenseA.href: ");
  646. console.log("'",licenseA.href, "'");
  647. }
  648. info.licenseHref = licenseA.href;
  649. if (info.licenseHref.slice(-3, -2) == ".") {
  650. exactLicenseVersion = licenseA.href.slice(-4, -1);
  651. }
  652. else {
  653. console.warn("slice at -3 is not .: " + info.licenseHref.slice(-3, -2));
  654. }
  655. }
  656. else {
  657. info.author = licenseA.textContent;
  658. info.authorHref = licenseA.href;
  659. if (verbose) {
  660. console.log("unused[]: " + licenseA.href);
  661. console.log("author: " + info.author);
  662. console.log("authorHref: " + info.authorHref);
  663. }
  664. }
  665. }
  666. }
  667. else {
  668. console.warn("There is no anchor with a class like "+licenseAnchorPrefix+"*");
  669. }
  670. return info;
  671. }
  672.  
  673. function addButton() {
  674. 'use strict';
  675. // This should run when ThingPage_galleryHeader* gets filled in, but only once to prevent an infinite loop.
  676. // var pageInfoEs = document.getElementsByClassName("item-page-info");
  677. // NOTE: now ThingiVerse is a React app, so you must use inspect to see the HTML.
  678. // "ThingPage__madeBy*" includes parts such as:
  679. // - `ThingPage__modelName*`
  680. // - `<div class="ThingPage__createdBy*">by <a ...>UserName</a>MON D, YYYY`
  681. var pageInfoE = getButtonContainer();
  682. if (pageInfoE == null) {
  683. console.log('The '+madeSpanClassName+' class was not found so the button wasn\'t added!');
  684. return;
  685. }
  686. // pageInfoE.innerHTML += "<button onclick=\"getRemixLicense()\">Copy License for Remix</button>";
  687. //or:
  688. // See https://www.w3schools.com/jsref/met_document_createelement.asp
  689. var btn = document.createElement("BUTTON"); // Create a <button> element
  690. btn.setAttribute("class", "button button-secondary"); // as of 2023 button class names are obfuscated :(
  691. // btn.setAttribute("style", "background-color: rgb(50%, 50%, 50%)");
  692. var btnText = "Copy License for Remix";
  693. btn.innerHTML = btnText; // Insert text
  694. // Any URL starting with a slash comes after: "https://creativecommons.org/licenses"
  695. // otherwise it comes after "https://"
  696. // - A list of CC licenses is at <https://creativecommons.org/about/cclicenses/>.
  697.  
  698. btn.addEventListener("click", function(){
  699. btn.innerHTML = btnText;
  700. var info = getInfo();
  701. populateCorrespondingLicenseFields(info);
  702. var markdownStr = getMarkdown(info);
  703. setClipboardText(markdownStr, btn);
  704. }); // end addEventListener click
  705. pageInfoE.appendChild(btn); // Append <button> for Markdown to whatever element was selected.
  706. }//end addButton
  707. function checkIfComplete() {
  708. // console.log("Monitoring page loading...");
  709. var missing_errors = "";
  710. var containers = getDivsWhereClassStartsWith(madeDivClassName);
  711. // console.log("Checking for completed page content...");
  712. if (containers.length == 1) {
  713. if (!elementHasAllPrefixes(containers[0], doneDivPrefixes)) {
  714. missing_errors += "containers[0].className " + containers[0].className + " with all of " + JSON.stringify(doneDivPrefixes) + ". ";
  715. if (verbose) {
  716. // console.log("The "+containers[0].className+" container is not complete:");
  717. // console.log("The document is not ready yet ("+containers[0].className+" does not contain the classes with the prefixes \""+JSON.stringify(doneDivPrefixes)+"\").");
  718. }
  719. }
  720. }
  721. else {
  722. missing_errors += "any container (required). ";
  723. // console.log("The page is not formatted as expected:");
  724. // console.log(containers.length + " is an unexpected count for divs with a class named like " + madeDivClassName + "*.");
  725. }
  726. if (!hasAllDivPrefixes(doneDivPrefixesMain)) {
  727. missing_errors += "hasAllDivPrefixes("+JSON.stringify(doneDivPrefixesMain)+"). ";
  728. // console.log("The document is not complete:");
  729. // console.log("The document is not ready yet (the document does not contain the class(es) with the prefix(es) \""+JSON.stringify(doneDivPrefixesMain)+"\").");
  730. }
  731. if (missing_errors.length == 0) {
  732. if (verbose) {
  733. console.log("The page has loaded.");
  734. }
  735. clearInterval(checkTimer);
  736. addButton();
  737. console.log("The license detection will resume after a user clicks the copy license button.");
  738. }
  739. else {
  740. console.log("The document is not ready (or is a new format). It is missing: "+missing_errors);
  741. }
  742. }
  743. checkTimer = setInterval(checkIfComplete, 2000);
  744. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址