Greasy Fork is available in English.

Flickr Original Link

Show direct links to download biggest Flickr image available and some other sizes.

  1. // ==UserScript==
  2. // @name Flickr Original Link
  3. // @namespace https://greasyfork.org/scripts/1190-flickr-original-link
  4. // @include /flickr\.com/
  5. // @version 6.0
  6. // @grant GM_getValue
  7. // @grant GM_setValue
  8. // @grant GM_addStyle
  9. // @require https://code.jquery.com/jquery-3.3.1.min.js
  10. // @description Show direct links to download biggest Flickr image available and some other sizes.
  11. // ==/UserScript==
  12. var postfix = "_d.jpg";
  13. var prefix = "DOWNLOAD ";
  14. var isChecked_openLink = "";
  15. var isChecked_alwaysShow = "";
  16. var key_openLink = "flickr_openLink";
  17. var key_alwaysShow = "flickr_alwaysShow";
  18. var value_openLink = false;
  19. var value_alwaysShow = false;
  20. var imageSizeOrder = ["9k","8k","7k","6k","5k","4k","3k","2k","o", "k", "h", "l","b", "c", "z"];
  21. var globalObserver = null;
  22.  
  23. function log(s) {
  24. console.log(s);
  25. }
  26. log("Begin flickr script");
  27.  
  28. function getSetting() {
  29. log("Begin get settings");
  30. value_openLink = GM_getValue(key_openLink, false);
  31. value_alwaysShow = GM_getValue(key_alwaysShow, false);
  32. if (value_openLink) {
  33. postfix = ".";
  34. isChecked_openLink = ' checked="checked" ';
  35. prefix = "OPEN ";
  36. }
  37. else {
  38. postfix = "_d.";
  39. isChecked_openLink = "";
  40. prefix = "DOWNLOAD ";
  41. }
  42. if (value_alwaysShow) {
  43. isChecked_alwaysShow = ' checked="checked" ';
  44. }
  45. else {
  46. isChecked_alwaysShow = "";
  47. }
  48. }
  49.  
  50. function checkAlwaysShow() {
  51. if (value_alwaysShow) {
  52. $('div.interaction-view').css('opacity', '1');
  53. }
  54. $('div.interaction-bar').css('bottom', '1.1em');
  55. }
  56.  
  57. function action_single_page() {
  58. if ($('.commonButton').length > 0) return false;
  59. var action = function (sourceCode) {
  60. var sizes = sourceCode.match(/modelExport: {.+?"sizes":{.+?}}/i);
  61. var mSize = sizes[0].match(/"width":"?\d+"?,"height":"?\d+"?,/ig);
  62. var mLink = sizes[0].match(/"displayUrl":"[^"]+"/ig);
  63. var length = mLink.length;
  64. for (var k = 0; k < length; k++) {
  65. mSize[k] = mSize[k].replace(/"width":(\d+),"height":(\d+),/i, "$1 x $2");
  66. mLink[k] = mLink[k].replace(/"displayUrl":"([^"]+)"/i, "$1").replace(/\\/g, "").replace(/(_[0-9a-z]+)\.([a-z]{3,4})/i, '$1' + postfix + '$2');
  67. }
  68. var insertLocation = $('.sub-photo-right-row1').filter(':first');
  69. var str = '<div><a class="commonButton bigButton" href="' + mLink[length - 1] + '">' + prefix + mSize[length - 1] + ' px</a>';
  70. for (k = length - 2; k >= length - 7 && k >= 0; --k) {
  71. str += '<a class="commonButton smallButton" href="' + mLink[k] + '">' + mSize[k] + ' px</a>';
  72. }
  73. str+="</div>";
  74. //2024.08.16: Add button closer to the top of right column. Sometimes the button is not displayed, just reload the page.
  75. insertLocation.prop('outerHTML',str+insertLocation.prop('outerHTML'));
  76. };
  77. $.get(document.URL, action);
  78. }
  79.  
  80. function getLinkFromSource(data) {
  81. if (data === null) return;// source code is not loaded, or empty, or has nothing good
  82. var sizes = data.match(/"sizes":.+?}}/ig);
  83. if (sizes === null) return false; // source code is not loaded, or empty, or has nothing good
  84. var dates = data.match(/"datePosted":"\d+"/ig);
  85. if (dates == null) {
  86. log("cannot find any dates");
  87. }
  88. //default: type == 'normal'
  89. var e2 = $('div.photo-list-photo-view');
  90. //2024.08.15: apply to new album page
  91. if (type == 'album')
  92. {
  93. e2 = $('div.photo-card-view div.foot');
  94. }
  95. log("Number of photo in this page: "+e2.length);
  96. for (var index = 0; index < e2.length; index++) {
  97. var e = $(e2[index]);
  98. if (e.find('.myFuckingLink').filter(':first').length > 0) continue;
  99. e.html(e.html() + '<a class="myFuckingLink"></a>');
  100. for (var i = 0; i < imageSizeOrder.length; ++i) {
  101. var photo = sizes[index].match(new RegExp('"' + imageSizeOrder[i] + '".*?:{"displayUrl":"([^"]+)","width":(\\d+),"height":(\\d+)', "i"));
  102. if (photo === null) continue;
  103.  
  104. var b = e.find('.myFuckingLink');
  105.  
  106. //b.attr('href', "http:" + photo[1].replace(/\\/g, "").replace(/(_[a-z])\.([a-z]{3,4})/i, '$1' + postfix + '$2'));
  107. //new flickr update 4/4/2019. Links now included https and are full address.
  108. b.attr('href', photo[1].replace(/\\/g, "").replace(/(_[0-9a-z]+)\.([a-z]{3,4})/i, '$1' + postfix + '$2'));
  109.  
  110. var timestamp = dates[index].match(/\d+/i);
  111. var t = new Date((new Number(timestamp)) * 1000);
  112. b.attr('title', prefix + photo[2] + " x " + photo[3] + " | Upload: " + t.toLocaleDateString());
  113.  
  114. b.html(prefix + photo[2] + " x " + photo[3]);
  115. break;
  116. }
  117. }
  118. }
  119.  
  120. function action_normal_page(theType) {
  121. var target = $('#content')[0];
  122. var config = {
  123. childList: true,
  124. subtree: true,
  125. };
  126. var prevUrl = "none";
  127. var prevThumbLength = 0;
  128. var sourceCode = null;
  129.  
  130. var action = function (x) {
  131. //default type == 'normal'
  132. var e3 = $('div.photo-list-photo-view');
  133. //2024.08.16: Album page is different
  134. if (x == 'album')
  135. {
  136. e3 = $('div.photo-card-view');
  137. }
  138. if (document.URL == prevUrl) {
  139. if (e3.length == prevThumbLength) return false; // number of thumbnail is not change, no need to process further
  140. checkAlwaysShow();
  141. prevThumbLength = e3.length;
  142. log("Number of thumb: " + prevThumbLength);
  143. // source code is get, use it now
  144. getLinkFromSource(sourceCode);
  145. }
  146. else {
  147. var e1 = e3.find('a').filter(':first');
  148. if (e1.length < 1) return false; // not found any link to valid single image page
  149. checkAlwaysShow();
  150. // get full source code for this page
  151. sourceCode = null;
  152. prevUrl = document.URL;
  153. var link1 = e1.attr('href');
  154. console.time("GetSource");
  155. $('#content').append('<div id="loadingIndicator" style="position:fixed;left:5px;bottom:2em;display:block;background-color:pink;border:solid;padding:3px">Getting original link<br>Please wait...</div>');
  156. log("Begin get source 1: " + link1);
  157. $.get(link1, function (data) {// process single image page source to get entry-type link
  158. var link2 = data.match(/<a\s+class=.+?entry-type.+?href='([^']+)/i)[1];
  159. link2 = link2.replace("/albums/", "/sets/");
  160. log("Begin get source 2: " + link2);
  161. $.get(link2, function (data) {// process page source to get image links
  162. log("Got final source: " + link2);
  163. console.timeEnd("GetSource");
  164. $('#loadingIndicator').remove();
  165. sourceCode = data;
  166. getLinkFromSource(sourceCode);
  167. });
  168. });
  169. }
  170. };
  171. action(theType);
  172. globalObserver = new MutationObserver(function (mutations, ob) {
  173. action(theType);
  174. });
  175. globalObserver.observe(target, config);
  176. }
  177.  
  178. function flickr_mouseenter() {
  179. var e = $(this);
  180. if (e.find('.myFuckingLink').filter(':first').length > 0) {
  181. e.off('mouseenter');
  182. return false;
  183. }
  184. var url = e.find('a').filter(':first').attr('href');
  185. if (typeof url == "undefined" || url === null) return false;
  186. e.append('<a class="myFuckingLink">(Link loading...)</a>');
  187. $.get(url, function (data) {
  188. var photo = data.match(/"displayUrl":"([^"]+)","width":(\d+),"height":(\d+)[^}]+}}/i);
  189. var link = photo[1].replace(/\\/g, "").replace(/(_[0-9a-z]+)\.([a-z]{3,4})/i, '$1' + postfix + '$2');
  190. var text = prefix + photo[2] + " x " + photo[3] + " Upload: ";
  191. var b = e.find('.myFuckingLink');
  192. b.attr('href', link);
  193. b.attr('title', text);
  194. b.html(text);
  195. });
  196. }
  197.  
  198. function action_hover_page() {
  199. var target = $('body')[0];
  200. var config = {
  201. childList: true,
  202. subtree: true,
  203. };
  204. var prevLength = 0;
  205. globalObserver = new MutationObserver(function (mutations, ob) {
  206. var e = $('div.photo-list-photo-view');
  207. if (e.length == prevLength) return false; // no new Thumbnail, don't do anything
  208. log("Number of thumb: " + e.length);
  209. prevLength = e.length;
  210. checkAlwaysShow();
  211. e.mouseenter(flickr_mouseenter);
  212. });
  213. globalObserver.observe(target, config);
  214. }
  215.  
  216. function pageType() {
  217. var t = "none";
  218. var htmlClass = $('html').attr('class');
  219. console.log("HTML class: " + htmlClass);
  220. if (htmlClass.match(/html-photo-page.+scrappy-view/i) !== null) t = 'single';
  221. else if (htmlClass.match(/html-(search-photos-unified|group-pool)-page-view/i) !== null) t = 'hover';
  222. else if ($('div.photo-list-photo-view').filter(':first').length > 0) t = 'normal';
  223. else if ($('div.photo-list-view').filter(':first').length > 0) t = 'album';
  224. console.log("Page type: " + t);
  225. return t;
  226. }
  227.  
  228. var prevType = "none";
  229. var type = "none";
  230. var oldUrl = "none";
  231. var count = 0;
  232.  
  233. function kickStart() {
  234. oldUrl = document.URL;
  235. type = pageType();
  236. getSetting();
  237. checkAlwaysShow();
  238. //default type == 'normal'
  239. var strCss = ".myFuckingLink{position:absolute;z-index:999999;left:3px;bottom:0px;width:100%;display:block;color:white!important;} .myFuckingLink:hover{background-color:rgba(100, 100, 255,0.65)!important} .commonButton{display: inline-block;border-radius: 0.5em;margin: 0.2em;padding: 0.5em;font-size: 90%;height: fit-content;} .bigButton{background-color: lightgreen} .smallButton{background-color:pink}";
  240. //2024.08.16: Album page is different
  241. if (type == 'album')
  242. {
  243. strCss = ".myFuckingLink{position:absolute;z-index:999999;left:5px;bottom:0px;width:100%;display:block;} .myFuckingLink:hover{background-color:rgba(100, 100, 255,0.65)!important} .commonButton{display: inline-block;border-radius: 0.5em;margin: 0.2em;padding: 0.5em;font-size: 90%;height: fit-content;} .bigButton{background-color: lightgreen} .smallButton{background-color:pink}";
  244. }
  245. GM_addStyle(strCss);
  246. $('.myOptionBox').remove();
  247. $('ul.nav-menu:first').append('<li class="myOptionBox"><div style="color:pink;padding:1px"><input id="optionbox_openLink" type="checkbox"' + isChecked_openLink + 'style="margin:2px"/>Open image link in browser<br><input id="optionbox_alwaysShow" type="checkbox"' + isChecked_alwaysShow + 'style="margin:2px"/>Always show image information in Photostream</div></li>');
  248. $('#optionbox_openLink').change(function () {
  249. GM_setValue(key_openLink, $(this).prop('checked'));
  250. });
  251. $('#optionbox_alwaysShow').change(function () {
  252. GM_setValue(key_alwaysShow, $(this).prop('checked'));
  253. });
  254. if (type == 'single') action_single_page();
  255. else if (type == 'normal' || type == 'album') action_normal_page(type);
  256. else if (type == 'hover') action_hover_page();
  257. }
  258.  
  259. // kickstart
  260. log("Kickstart");
  261.  
  262. var timestamp = "1469473824";
  263. var number = new Number(timestamp);
  264. var t = new Date(number * 1000);
  265. log("Time number: " + t.toLocaleDateString('vi-VN'));
  266.  
  267. kickStart();
  268.  
  269. var target = $('html')[0];
  270. var config = {
  271. childList: false,
  272. attributes: true,
  273. };
  274. var observer = new MutationObserver(function (mutations, ob) {
  275. if (oldUrl == document.URL) return;
  276. if (globalObserver !== null) globalObserver.disconnect();
  277. kickStart();
  278. });
  279. observer.observe(target, config);