Coppersmina

Enhances Coppermine galleries with direct links, color coded border and other tricks. See source code for more info.

当前为 2014-08-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Coppersmina
  3. // @namespace github.com/ariacorrente/coppersmina
  4. // @description Enhances Coppermine galleries with direct links, color coded border and other tricks. See source code for more info.
  5. // @version 0.8
  6. // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
  7. // @match http://*/*
  8. // @require https://greasyfork.org/scripts/2855-gm-config/code/GM_config.js?version=8032
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_openInTab
  13. // ==/UserScript==
  14.  
  15. /*
  16. Copyright (C) 2014 ariacorrente
  17.  
  18. This program is free software: you can redistribute it and/or modify
  19. it under the terms of the GNU General Public License as published by
  20. the Free Software Foundation, either version 3 of the License, or
  21. (at your option) any later version.
  22.  
  23. This program is distributed in the hope that it will be useful,
  24. but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. GNU General Public License for more details.
  27.  
  28. You should have received a copy of the GNU General Public License
  29. along with this program. If not, see <http://www.gnu.org/licenses/>.
  30.  
  31. Contact: https://github.com/ariacorrente/coppersmina
  32. */
  33.  
  34. /*
  35. # Coppersmina
  36.  
  37. User script that enhances Coppermine galleries with direct links, color coded
  38. border and other tiny features.
  39.  
  40. Allows the use of mass downloaders like DownThemAll! and FlashGot.
  41.  
  42. Project repository:
  43.  
  44. https://github.com/ariacorrente/coppersmina
  45.  
  46. ## Features
  47.  
  48. - Configuration dialog accessible through menu command
  49. - Replace links in thumbnails to point directly to the high defintion image
  50. - Add a colored border indicating the size of the image
  51. - Append image information into the thumbnail's caption
  52. - Add a link to open the thumbnail's parent album into a new tab
  53. - Remove the tooltip from the thumbnails
  54. - The "always run" feature allows the execution of the script even if the site
  55. is not automatically detected as a Coppermine gallery
  56.  
  57. ## Performance and security
  58.  
  59. AKA: Why is this script running on every page?
  60.  
  61. This script will run on every page because the domain name is not usefull to
  62. identify an installed Coppermine gallery in the website.
  63.  
  64. Before doing the heavy work the script will try to detect if the page is a
  65. Coppermine gallery. The script scans the page for anchors containing the string
  66. "coppermine".
  67.  
  68. ## "Always run" feature
  69.  
  70. The "always run" feature allows the execution of the heavy work even if the
  71. script failed to detect the site as Coppermine gallery. This is usefull when
  72. the webmaster changed the web pages resulting in a failed detection.
  73.  
  74. This option is domain specific because it's saved in local storage. When set for
  75. a domain it will be remembered only for the current domain.
  76.  
  77. Running the script in a non-coppermine site may cause problems and make some
  78. sites misbehave.
  79. */
  80.  
  81. (function () {
  82. var colorCode = [{size: 0, color: 'lightgray'},
  83. {size: 250, color: 'lightgreen'},
  84. {size: 500, color: 'yellow'},
  85. {size: 1000, color: 'red'},
  86. {size: 2000, color: 'magenta'}];
  87.  
  88. var debugMode = false;
  89.  
  90. function clog(msg) {
  91. if (debugMode) {
  92. console.log(msg);
  93. }
  94. }
  95.  
  96. //Detect if this site is a Coppermine gallery
  97. var coppermineDetected = document.querySelector('a[href*=coppermine]') !== null;
  98.  
  99. //runAlways is handled by localstorage so it's domain specific
  100. var runAlways = localStorage.getItem("runAlways") === "true";
  101.  
  102. //Settings dialog will be inserted in configFrame
  103. var configFrame;
  104. GM_config.init({
  105. "id": "GM_config",
  106. "title": "Coppersmina - Settings",
  107. "fields": {
  108. "colorBorder": {
  109. "section": ["Border of thumbnails", "Add a color coded border based on the size of the image. From smaller to bigger the colors are sorted in the order \
  110. <span class='colorLevel0'>small</span> < \
  111. <span class='colorLevel1'>interesting</span> < \
  112. <span class='colorLevel2'>nice</span> < \
  113. <span class='colorLevel3'>big</span> < \
  114. <span class='colorLevel4'>huge"],
  115. "label": "Enable colored borders",
  116. "labelPos": "right",
  117. "type": "checkbox",
  118. "default": true
  119. },
  120. "colorByWhat": {
  121. "label": "Color based on the image feature:",
  122. "type": "select",
  123. "title": "Dimensions will use the image width and height\nFilesize will use the size of the file.",
  124. "options": ["Dimensions", "Filesize"],
  125. "default": "Dimensions"
  126. },
  127. "borderSize": {
  128. "label": "Border size:",
  129. "title": "Width in pixels of the border drawn around the thumbnails",
  130. "type": "int",
  131. "min": 0,
  132. "size": 5,
  133. "default": 3
  134. },
  135. "clearOldCaption": {
  136. "section": ['Captions', 'Remove or add information to the captions below the thumbnails.'],
  137. "label": "Clear original captions",
  138. "labelPos": "right",
  139. "type": "checkbox",
  140. "default": true
  141. },
  142. "ciShowFilesize": {
  143. "label": "Show file size",
  144. "labelPos": "right",
  145. "type": "checkbox",
  146. "default": true
  147. },
  148. "ciShowDimensions": {
  149. "label": "Show image dimensions",
  150. "labelPos": "right",
  151. "type": "checkbox",
  152. "default": false
  153. },
  154. "ciShowDateAdded": {
  155. "label": "Show date added",
  156. "labelPos": "right",
  157. "type": "checkbox",
  158. "default": false
  159. },
  160. "ciShowAlbumLink": {
  161. "label": "Show link to open the album",
  162. "labelPos": "right",
  163. "title": "Show a link that will open the page of the the parent album. The opening will be slower than usual because the script must do two HTTP requests to the server.",
  164. "type": "checkbox",
  165. "default": true
  166. },
  167. "ciShowOriginalLink": {
  168. "label": "Sow original link",
  169. "labelPos": "right",
  170. "title": "The original link points to the medium resolution image",
  171. "type": "checkbox",
  172. "default": true
  173. },
  174. "removeTooltips": {
  175. "section": ['Misc'],
  176. "label": "Remove tooltips from thumbnails",
  177. "title": "If interesting data is moved inside the caption, the tooltip is no more required",
  178. "labelPos": "right",
  179. "type": "checkbox",
  180. "default": true
  181. },
  182. "runAlways": {
  183. "label": 'Run always on "' + window.location.hostname + '"',
  184. "labelPos": "right",
  185. "title": "This is the only domain specific option.\nEnabling this option will force Coppermina to process the page even if it has not been detected as Coppermine gallery.",
  186. "type": "checkbox",
  187. "default": false,
  188. "save": false //this is handled by localstorage
  189. }
  190. },
  191. "events": {
  192. "save": function () {
  193. var savedRunAlways = GM_config.fields.runAlways.toValue();
  194. if (savedRunAlways === true) {
  195. localStorage.runAlways = "true";
  196. } else {
  197. localStorage.removeItem("runAlways");
  198. }
  199. clog("Saved field runAlways: " + savedRunAlways);
  200. //Reload to apply changes to page
  201. window.location.reload(false);
  202. },
  203. "init": function () {
  204. //Manually load runAlways config from local storage
  205. GM_config.fields.runAlways.value = localStorage.getItem("runAlways") === "true";
  206. },
  207. "open": function () {
  208. //On opening add an extra heading to display what will be done
  209. var configHeader = document.getElementById("GM_config_header");
  210. if (configHeader === null) {
  211. clog("Config header not found");
  212. return;
  213. }
  214. var configInfo = document.createElement("div");
  215. configInfo.innerHTML = "What will Coppermina do on this site:";
  216. var spanResult = document.createElement("span");
  217. var pComment = document.createElement("p");
  218. if (coppermineDetected) {
  219. spanResult.innerHTML = "Execute automatically";
  220. spanResult.className = "positive";
  221. pComment.innerHTML = "Coppermina will execute on this site because it has been detected as a Coppermine gallery after scanning the page content.<br /> If some features are not working it may be caused by a non standard Coppermine configuration.";
  222. } else if (runAlways) {
  223. spanResult.innerHTML = "Execute forced";
  224. spanResult.className = "forced";
  225. pComment.innerHTML = "Coppermina will execute on this site even if it's not detected as a Coppermine gallery. This is caused by the option 'Run Always...'.";
  226. } else {
  227. spanResult.innerHTML = "Nothing";
  228. spanResult.className = "negative";
  229. pComment.innerHTML = "Coppermina will do nothing on this site. If you want to force the execution you must toggle the option 'Run always on...' and then save to reload the page.";
  230. }
  231. configInfo.appendChild(spanResult);
  232. configInfo.appendChild(document.createElement("br"));
  233. configInfo.appendChild(pComment);
  234. configHeader.appendChild(configInfo);
  235. }
  236. },
  237. "css": 'div#GM_config_header div { font-size: 13pt; margin: 10px auto 10px; padding: 5px } \
  238. div#GM_config_header div span { padding: 5px } \
  239. span.positive { color: green } \
  240. span.forced { color: orange } \
  241. span.negative { color: black } \
  242. div#GM_config_header div p { font-size: 12px; text-align: left} \
  243. #GM_config_section_desc_0 span { display: inline-block } \
  244. div.dropShadow { box-shadow: 3px 3px 5px 5px #444 } \
  245. #GM_config div { padding: 0 5px 0 5px } \
  246. span.colorLevel0 { border: 2px solid ' + colorCode[0].color + '} \
  247. span.colorLevel1 { border: 2px solid ' + colorCode[1].color + '} \
  248. span.colorLevel2 { border: 2px solid ' + colorCode[2].color + '} \
  249. span.colorLevel3 { border: 2px solid ' + colorCode[3].color + '} \
  250. span.colorLevel4 { border: 2px solid ' + colorCode[4].color + '}'
  251. });
  252.  
  253. var captionInfo = [];
  254. var clearOldCaption = GM_config.get("clearOldCaption");
  255. if (GM_config.get("ciShowFilesize")) {
  256. captionInfo.push("Filesize");
  257. }
  258. if (GM_config.get("ciShowDimensions")) {
  259. captionInfo.push("Dimensions");
  260. }
  261. if (GM_config.get("ciShowDateAdded")) {
  262. captionInfo.push("Date added");
  263. }
  264. if (GM_config.get("ciShowOriginalLink")) {
  265. captionInfo.push("Original link");
  266. }
  267. if (GM_config.get("ciShowAlbumLink")) {
  268. captionInfo.push("Album link");
  269. }
  270. var colorBorder = GM_config.get("colorBorder");
  271. var colorByWhat = GM_config.get("colorByWhat");
  272. var borderSize = GM_config.get("borderSize");
  273. var removeTooltips = GM_config.get("removeTooltips");
  274.  
  275. function openConfig() {
  276. if (!configFrame) {
  277. configFrame = document.createElement('div');
  278. configFrame.className = "dropShadow";
  279. document.body.appendChild(configFrame);
  280. }
  281. GM_config.init({"id": "GM_config", "frame": configFrame});
  282. GM_config.open();
  283. }
  284. GM_registerMenuCommand("Coppersmina - Settings", openConfig, "C");
  285.  
  286. /*
  287. * Load in a new tab the parent album
  288. * 1- query the server with the old link page
  289. * 2- extract the parent album link
  290. * 3- open a new tab with the album link
  291. */
  292. function loadAlbum(event) {
  293. var anchors,//anchors to be searched
  294. xhr, //XML http request
  295. regex, //regex tool
  296. found, //regex result
  297. i, //iterator over regex results
  298. anchor; //a element being analyzed
  299. if (event.button === 2) {
  300. //Do nothing for right mouse click
  301. return true;
  302. }
  303. xhr = new XMLHttpRequest();
  304. xhr.open("GET", this.dataset.href, true);
  305. xhr.onload = function () {
  306.  
  307. anchors = xhr.responseXML.querySelectorAll('a[href*=thumbnails]');
  308. clog("Album anchors found:" + anchors.length);
  309. found = false;
  310. for (i = 0; i < anchors.length && found === false; i++) {
  311. //Search for an url like:
  312. //http://coppermine-gallery.net/demo/cpg15x/thumbnails.php?album=2
  313. anchor = anchors[i];
  314. clog("Regexing " + anchor.href);
  315. regex = new RegExp(/thumbnails\.php\?album=[0-9]+$/);
  316. found = regex.test(anchor.href);
  317. clog("Regex result: " + found);
  318. }
  319.  
  320. if (found) {
  321. if (event.button === 0) {
  322. //Left mouse click
  323. location.href = anchor.href;
  324. } else if (event.button === 1) {
  325. /* Middle mouse click
  326. * This will open a new tab in background only if
  327. * "When I open a link in a new tab, switch to it immediately"
  328. * in firefox settings or "browser.tabs.loadInBackground" in
  329. * about:config is set to false. False is the default value.
  330. */
  331. GM_openInTab(anchor.href);
  332. }
  333. } else {
  334. clog("Album link not found");
  335. }
  336. };
  337. xhr.responseType = 'document';
  338. xhr.send();
  339. //Avoid default behaviour
  340. return false;
  341. }
  342.  
  343. function runCoppersmina() {
  344. var i, //index to iterate anchors
  345. j, //index to iterate captionInfo
  346. tableCell, //td element containing the anchor
  347. cellElements, //all the elements in tableCell
  348. anchors, //all the anchors containing thumbnails
  349. anchor, //a element being analyzed
  350. thumbnail, //img element inside anchor
  351. caption, //span element below the thumbnail
  352. regex, //regular expression searching thumbnail's title
  353. found, //regex result
  354. extraInfo, //element to append into caption
  355. imageWeight, //quantity to use to choose thumbnail's border color
  356. mustAddNewline, //requred to handle newlines in captions
  357. newColor; //color to use for thumbnail's border
  358.  
  359. //find all the anchors around the the thumbnails and iterate
  360. anchors = document.querySelectorAll('a[href*=displayimage]');
  361. clog("Found " + anchors.length + " anchors");
  362. for (i = 0; i < anchors.length; i++) {
  363. anchor = anchors[i];
  364. clog("Working on anchor: " + anchor.href);
  365. thumbnail = anchor.querySelector('img');
  366. if (thumbnail === null) {
  367. clog("Thumbnail not found");
  368. continue;
  369. }
  370.  
  371. tableCell = anchor.parentNode;
  372. //find the text field under the thumbnail
  373. //using the class thumb_title instead of thumb_caption usually
  374. //gives a better integration with the sites styles
  375. caption = tableCell.querySelector("span.thumb_title");
  376. if (caption === null) {
  377. clog("Caption not found, adding a new one");
  378. caption = document.createElement('span');
  379. caption.className = "thumb_title";
  380. tableCell.appendChild(caption);
  381. }
  382.  
  383. //maybe clear the caption
  384. if (clearOldCaption) {
  385. //Clear content of caption
  386. while (caption.firstChild) {
  387. caption.removeChild(caption.firstChild);
  388. }
  389. //Clear other captions sibilings of our caption
  390. cellElements = tableCell.childNodes;
  391. for (j = 0; j < cellElements.length; j++) {
  392. if (cellElements[j] !== caption && cellElements[j] !== anchor) {
  393. tableCell.removeChild(cellElements[j]);
  394. j--;
  395. }
  396. }
  397. }
  398.  
  399. //Add info to the caption
  400. mustAddNewline = clearOldCaption === false;
  401. for (j = 0; j < captionInfo.length; j++) {
  402.  
  403. if (mustAddNewline) {
  404. caption.appendChild(document.createElement('br'));
  405. }
  406.  
  407. if (captionInfo[j] === 'Original link') {
  408. //add the old link to the caption
  409. extraInfo = document.createElement('a');
  410. extraInfo.innerHTML = "Original link";
  411. extraInfo.href = anchor.href;
  412. caption.appendChild(extraInfo);
  413. mustAddNewline = true;
  414. continue;
  415. }
  416. if (captionInfo[j] === "Album link") {
  417. /*
  418. * Add a link to open the album
  419. * Can't use a normal anchor because is impossible to avoid
  420. * opening a new tab with the anchor href if the user middle
  421. * clicks.
  422. * To handle correctly left and middle clicks with js the
  423. * href attribute is empty and the url is passed using
  424. * HTML5's data-*. To catch also middle clicks listen
  425. * to mouseup instead of click event.
  426. */
  427. extraInfo = document.createElement('a');
  428. extraInfo.innerHTML = "Open album";
  429. extraInfo.style = "cursor: pointer";
  430. extraInfo.dataset.href = anchor.href;
  431. extraInfo.addEventListener("mouseup", loadAlbum, false);
  432. extraInfo.title = "Open the thumbnail's album.";
  433. caption.appendChild(extraInfo);
  434. mustAddNewline = true;
  435. continue;
  436. }
  437. regex = new RegExp(captionInfo[j] + '=(.*)');
  438. found = regex.exec(thumbnail.title);
  439. if (found !== null) {
  440. extraInfo = document.createElement('span');
  441. extraInfo.innerHTML = found[1];
  442. caption.appendChild(extraInfo);
  443. mustAddNewline = true;
  444. } else {
  445. clog('Image info "' + captionInfo[j] + '" not found');
  446. mustAddNewline = false;
  447. }
  448. }
  449.  
  450. //replace the thumbnail link with a direct link to the HD image
  451. anchor.href = thumbnail.src.replace(/thumb_/, "");
  452.  
  453. if (colorBorder) {
  454. //Calculate image weight to chose a border color
  455. regex = new RegExp(colorByWhat + '=(.*)');
  456. found = regex.exec(thumbnail.title);
  457. if (found) {
  458. if (colorByWhat === "Dimensions") {
  459. var sizes = found[1].split('x');
  460. var area = sizes[0] * sizes[1];
  461. //Divide to have comparable sizes with "FileSize" wich is expressed in KB
  462. imageWeight = area / 8192;
  463. } else {
  464. //Remove last 3 characters occupied by "KiB"
  465. imageWeight = found[1].slice(0, -3);
  466. }
  467. } else {
  468. clog('Image info "' + colorByWhat + '" not found, unable to color the border');
  469. }
  470.  
  471. //Add the colored border to the thumbnail
  472. for (j = 0; j < colorCode.length; j++) {
  473. if (imageWeight > colorCode[j].size) {
  474. newColor = colorCode[j].color;
  475. }
  476. }
  477. thumbnail.style.border = borderSize + 'px solid ' + newColor;
  478. }
  479.  
  480. if (thumbnail.title === "") {
  481. thumbnail.title = "Coppersmina:\nNo colored border or extra information in caption because in this gallery the required data is not available.";
  482. } else if (removeTooltips) {
  483. //Remove the tooltip if required
  484. thumbnail.title = "";
  485. }
  486. }
  487. }
  488.  
  489. //Starts here
  490. if (runAlways || coppermineDetected) {
  491. clog("Coppersmining");
  492. runCoppersmina();
  493. }
  494.  
  495. }());