DsModDownloader

DS/DST mods directly downloader for tieba & steam

  1. // ==UserScript==
  2. // @author Aq
  3. // @name DsModDownloader
  4. // @namespace https://github.com/aqgithub/steamdl/blob/master/DsModDownloader.user.js
  5. // @description DS/DST mods directly downloader for tieba & steam
  6. // @include *steamcommunity.com/sharedfiles/filedetails/?id=*
  7. // @include *tieba.baidu.com/p/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setClipboard
  10. // @grant GM_openInTab
  11. // @require http://code.jquery.com/jquery-1.12.0.min.js
  12. // @supportURL https://github.com/aqgithub/steamdl/issues
  13. // @version 0.1.2
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. /* FileSaver.js
  18. * A saveAs() FileSaver implementation.
  19. * 1.1.20151003
  20. *
  21. * By Eli Grey, http://eligrey.com
  22. * License: MIT
  23. * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
  24. */
  25. /*global self */
  26. /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
  27. /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
  28. var saveAs = saveAs || (function (view) {
  29. 'use strict';
  30. // IE <10 is explicitly unsupported
  31. if (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) {
  32. return;
  33. }
  34. var
  35. doc = view.document
  36. // only get URL when necessary in case Blob.js hasn't overridden it yet
  37. ,
  38. get_URL = function () {
  39. return view.URL || view.webkitURL || view;
  40. },
  41. save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'),
  42. can_use_save_link = 'download' in save_link,
  43. click = function (node) {
  44. var event = new MouseEvent('click');
  45. node.dispatchEvent(event);
  46. },
  47. is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent),
  48. webkit_req_fs = view.webkitRequestFileSystem,
  49. req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem,
  50. throw_outside = function (ex) {
  51. (view.setImmediate || view.setTimeout) (function () {
  52. throw ex;
  53. }, 0);
  54. },
  55. force_saveable_type = 'application/octet-stream',
  56. fs_min_size = 0
  57. // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
  58. // https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
  59. // for the reasoning behind the timeout and revocation flow
  60. ,
  61. arbitrary_revoke_timeout = 500 // in ms
  62. ,
  63. revoke = function (file) {
  64. var revoker = function () {
  65. if (typeof file === 'string') { // file is an object URL
  66. get_URL().revokeObjectURL(file);
  67. } else { // file is a File
  68. file.remove();
  69. }
  70. };
  71. if (view.chrome) {
  72. revoker();
  73. } else {
  74. setTimeout(revoker, arbitrary_revoke_timeout);
  75. }
  76. },
  77. dispatch = function (filesaver, event_types, event) {
  78. event_types = [
  79. ].concat(event_types);
  80. var i = event_types.length;
  81. while (i--) {
  82. var listener = filesaver['on' + event_types[i]];
  83. if (typeof listener === 'function') {
  84. try {
  85. listener.call(filesaver, event || filesaver);
  86. } catch (ex) {
  87. throw_outside(ex);
  88. }
  89. }
  90. }
  91. },
  92. auto_bom = function (blob) {
  93. // prepend BOM for UTF-8 XML and text/* types (including HTML)
  94. if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
  95. return new Blob(['',
  96. blob], {
  97. type: blob.type
  98. });
  99. }
  100. return blob;
  101. },
  102. FileSaver = function (blob, name, no_auto_bom) {
  103. if (!no_auto_bom) {
  104. blob = auto_bom(blob);
  105. }
  106. // First try a.download, then web filesystem, then object URLs
  107.  
  108. var
  109. filesaver = this,
  110. type = blob.type,
  111. blob_changed = false,
  112. object_url,
  113. target_view,
  114. dispatch_all = function () {
  115. dispatch(filesaver, 'writestart progress write writeend'.split(' '));
  116. }
  117. // on any filesys errors revert to saving with object URLs
  118. ,
  119. fs_error = function () {
  120. if (target_view && is_safari && typeof FileReader !== 'undefined') {
  121. // Safari doesn't allow downloading of blob urls
  122. var reader = new FileReader();
  123. reader.onloadend = function () {
  124. var base64Data = reader.result;
  125. target_view.location.href = 'data:attachment/file' + base64Data.slice(base64Data.search(/[,;]/));
  126. filesaver.readyState = filesaver.DONE;
  127. dispatch_all();
  128. };
  129. reader.readAsDataURL(blob);
  130. filesaver.readyState = filesaver.INIT;
  131. return;
  132. }
  133. // don't create more object URLs than needed
  134.  
  135. if (blob_changed || !object_url) {
  136. object_url = get_URL().createObjectURL(blob);
  137. }
  138. if (target_view) {
  139. target_view.location.href = object_url;
  140. } else {
  141. var new_tab = view.open(object_url, '_blank');
  142. if (new_tab == undefined && is_safari) {
  143. //Apple do not allow window.open, see http://bit.ly/1kZffRI
  144. view.location.href = object_url
  145. }
  146. }
  147. filesaver.readyState = filesaver.DONE;
  148. dispatch_all();
  149. revoke(object_url);
  150. },
  151. abortable = function (func) {
  152. return function () {
  153. if (filesaver.readyState !== filesaver.DONE) {
  154. return func.apply(this, arguments);
  155. }
  156. };
  157. },
  158. create_if_not_found = {
  159. create: true,
  160. exclusive: false
  161. },
  162. slice
  163. ;
  164. filesaver.readyState = filesaver.INIT;
  165. if (!name) {
  166. name = 'download';
  167. }
  168. if (can_use_save_link) {
  169. object_url = get_URL().createObjectURL(blob);
  170. setTimeout(function () {
  171. save_link.href = object_url;
  172. save_link.download = name;
  173. click(save_link);
  174. dispatch_all();
  175. revoke(object_url);
  176. filesaver.readyState = filesaver.DONE;
  177. });
  178. return;
  179. }
  180. // Object and web filesystem URLs have a problem saving in Google Chrome when
  181. // viewed in a tab, so I force save with application/octet-stream
  182. // http://code.google.com/p/chromium/issues/detail?id=91158
  183. // Update: Google errantly closed 91158, I submitted it again:
  184. // https://code.google.com/p/chromium/issues/detail?id=389642
  185.  
  186. if (view.chrome && type && type !== force_saveable_type) {
  187. slice = blob.slice || blob.webkitSlice;
  188. blob = slice.call(blob, 0, blob.size, force_saveable_type);
  189. blob_changed = true;
  190. }
  191. // Since I can't be sure that the guessed media type will trigger a download
  192. // in WebKit, I append .download to the filename.
  193. // https://bugs.webkit.org/show_bug.cgi?id=65440
  194.  
  195. if (webkit_req_fs && name !== 'download') {
  196. name += '.download';
  197. }
  198. if (type === force_saveable_type || webkit_req_fs) {
  199. target_view = view;
  200. }
  201. if (!req_fs) {
  202. fs_error();
  203. return;
  204. }
  205. fs_min_size += blob.size;
  206. req_fs(view.TEMPORARY, fs_min_size, abortable(function (fs) {
  207. fs.root.getDirectory('saved', create_if_not_found, abortable(function (dir) {
  208. var save = function () {
  209. dir.getFile(name, create_if_not_found, abortable(function (file) {
  210. file.createWriter(abortable(function (writer) {
  211. writer.onwriteend = function (event) {
  212. target_view.location.href = file.toURL();
  213. filesaver.readyState = filesaver.DONE;
  214. dispatch(filesaver, 'writeend', event);
  215. revoke(file);
  216. };
  217. writer.onerror = function () {
  218. var error = writer.error;
  219. if (error.code !== error.ABORT_ERR) {
  220. fs_error();
  221. }
  222. };
  223. 'writestart progress write abort'.split(' ').forEach(function (event) {
  224. writer['on' + event] = filesaver['on' + event];
  225. });
  226. writer.write(blob);
  227. filesaver.abort = function () {
  228. writer.abort();
  229. filesaver.readyState = filesaver.DONE;
  230. };
  231. filesaver.readyState = filesaver.WRITING;
  232. }), fs_error);
  233. }), fs_error);
  234. };
  235. dir.getFile(name, {
  236. create: false
  237. }, abortable(function (file) {
  238. // delete file if it already exists
  239. file.remove();
  240. save();
  241. }), abortable(function (ex) {
  242. if (ex.code === ex.NOT_FOUND_ERR) {
  243. save();
  244. } else {
  245. fs_error();
  246. }
  247. }));
  248. }), fs_error);
  249. }), fs_error);
  250. },
  251. FS_proto = FileSaver.prototype,
  252. saveAs = function (blob, name, no_auto_bom) {
  253. return new FileSaver(blob, name, no_auto_bom);
  254. }
  255. ;
  256. // IE 10+ (native saveAs)
  257. if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
  258. return function (blob, name, no_auto_bom) {
  259. if (!no_auto_bom) {
  260. blob = auto_bom(blob);
  261. }
  262. return navigator.msSaveOrOpenBlob(blob, name || 'download');
  263. };
  264. }
  265. FS_proto.abort = function () {
  266. var filesaver = this;
  267. filesaver.readyState = filesaver.DONE;
  268. dispatch(filesaver, 'abort');
  269. };
  270. FS_proto.readyState = FS_proto.INIT = 0;
  271. FS_proto.WRITING = 1;
  272. FS_proto.DONE = 2;
  273. FS_proto.error =
  274. FS_proto.onwritestart =
  275. FS_proto.onprogress =
  276. FS_proto.onwrite =
  277. FS_proto.onabort =
  278. FS_proto.onerror =
  279. FS_proto.onwriteend =
  280. null;
  281. return saveAs;
  282. }(typeof self !== 'undefined' && self
  283. || typeof window !== 'undefined' && window
  284. || this.content
  285. ));
  286. // `self` is undefined in Firefox for Android content script context
  287. // while `this` is nsIContentFrameMessageManager
  288. // with an attribute `content` that corresponds to the window
  289. if (typeof module !== 'undefined' && module.exports) {
  290. module.exports.saveAs = saveAs;
  291. } else if ((typeof define !== 'undefined' && define !== null) && (define.amd != null)) {
  292. define([], function () {
  293. return saveAs;
  294. });
  295. }
  296. // FileSaver END //
  297. (function (w, d) {
  298. var API_GET_PUBLISHED_FILE_DETAILS = 'http://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v0001/';
  299. var curDomain = - 1; // 1 - tieba, 0 - steam
  300. var modDling = 0;
  301. function getDlUrl(modID, $dlButton) {
  302. GM_xmlhttpRequest({
  303. method: 'POST',
  304. url: API_GET_PUBLISHED_FILE_DETAILS,
  305. data: 'itemcount=1&publishedfileids[0]=' + modID + '&format=json',
  306. headers: {
  307. 'Content-Type': 'application/x-www-form-urlencoded'
  308. },
  309. onload: function (response) {
  310. data = $.parseJSON(response.responseText);
  311. var fileurl = data.response.publishedfiledetails[0].file_url;
  312. var filename = data.response.publishedfiledetails[0].title + '.zip';
  313. if (curDomain) {
  314. if (fileurl) {
  315. $dlButton.html('<span class="aq_copy_link">复制下载链接</span> | ').addClass('aq_dl_done').attr({
  316. 'aq_file_name': filename,
  317. 'aq_file_link': fileurl
  318. });
  319. $dlButton.append($('<a>', {
  320. href: fileurl,
  321. download: filename,
  322. 'class': 'aq_dl_btn',
  323. text: '直接下载'
  324. })).append(' | <span class="aq_dl_btn_2">重命名并下载(测试)</span>');
  325. }
  326. else {
  327. $dlButton.html('无法解析,mod可能已被移除');
  328. }
  329. }
  330. else {
  331. if (fileurl) {
  332. $('#aq-steam-dl-btn').on('click', function () {
  333. window.location.href = fileurl;
  334. });
  335. $('#aq-steam-dl-btn-2:not(.aq_dling)').on('click', function () {
  336. renameThenDl2($(this), fileurl, filename);
  337. });
  338. $('#aq-steam-copy-btn').on('click', function () {
  339. GM_setClipboard(fileurl);
  340. alert('已复制到剪贴板\n' + fileurl);
  341. });
  342. }
  343. else {
  344. $('.game_area_purchase_game>h1>span').text('mod下载地址解析失败,请重新加载页面');
  345. $('#aq-steam-dl-btn').hide();
  346. $('#aq-steam-dl-btn-2').hide();
  347. $('#aq-steam-copy-btn').hide();
  348. }
  349. }
  350. },
  351. onerror: function (reponse) {
  352. console.log('err: ' + reponse);
  353. }
  354. });
  355. }
  356. function dataToBlob(data, mimeString) {
  357. var buffer = new Int8Array(new ArrayBuffer(data.length));
  358. for (var i = 0; i < data.length; i++) {
  359. buffer[i] = data.charCodeAt(i) & 255;
  360. }
  361. try {
  362. return new Blob([buffer], {
  363. type: mimeString
  364. });
  365. } catch (e1) {
  366. try {
  367. var BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
  368. if (e.name == 'TypeError' && window.BlobBuilder) {
  369. bb = new BlobBuilder();
  370. bb.append([buffer.buffer]);
  371. return bb.getBlob(mimeString);
  372. } else if (e.name == 'InvalidStateError') {
  373. return new Blob([buffer.buffer], {
  374. type: mimeString
  375. });
  376. }
  377. } catch (e2) {
  378. }
  379. }
  380. return null;
  381. }
  382. function renameThenDl2($btn, fileurl, filename) {
  383. GM_xmlhttpRequest({
  384. method: 'GET',
  385. url: fileurl,
  386. 'overrideMimeType': 'text/plain; charset=x-user-defined',
  387. onload: function (d) {
  388. saveAs(dataToBlob(d.responseText, 'application/zip'), filename);
  389. }
  390. })
  391. }
  392. function htmlParser() {
  393. if (d.domain.toLowerCase().indexOf('baidu.com') > 0) {
  394. curDomain = 1;
  395. $(d).on('mouseover', '.d_post_content a:not(.aq_link_parsed),.lzl_content_main a:not(.aq_link_parsed)', function (e) {
  396. var linkToAnyls = $(this).text();
  397. var pattern = /steamcommunity.com\D*([0-9]{2,15})/i;
  398. var modID = pattern.exec(linkToAnyls);
  399. modID && $(this).after('<span class="aq_id aq_ori_link_hide" style="cursor:default;color:blue;margin-left:3px;margin-right:2px">' + modID[1] + '</span>|<span class="aq_dl" style="cursor:pointer;color:red;margin-left:2px;margin-right:2px;">解析中...</span>|<span class="aq_lnk" style="cursor:pointer;color:blue;margin-left:2px;margin-right:3px">转到steam页面</span>').addClass('aq_link_parsed').hide();
  400. getDlUrl(modID[1], $(this).next().next());
  401. });
  402. $(d).on('click', '.aq_id', function(e) {
  403. $idBtn = $(this);
  404. if ($idBtn.hasClass('aq_ori_link_hide')) {
  405. $idBtn.removeClass('aq_ori_link_hide').prev().show();
  406. }
  407. else {
  408. $idBtn.addClass('aq_ori_link_hide').prev().hide();
  409. }
  410. });
  411. $(d).on('click', '.aq_copy_link', function (e) {
  412. var fileLink = $(this).parent().attr('aq_file_link');
  413. GM_setClipboard(fileLink);
  414. alert('已复制到剪贴板\n' + fileLink);
  415. });
  416. $(d).on('click', '.aq_dl_btn_2:not(.aq_dling)', function (e) {
  417. modDling++;
  418. var $dlButton2 = $(this).addClass('aq_dling');
  419. var fileurl = $(this).prev().attr('href');
  420. var filename = $(this).prev().attr('download');
  421. $dlButton2.text('下载中...0%');
  422. var xhr = new XMLHttpRequest();
  423. xhr.onreadystatechange = function () {
  424. if (this.readyState == 4 && this.status == 200) {
  425. $dlButton2.text('重新下载(测试)').removeClass('aq_dling');
  426. modDling--;
  427. saveAs(this.response, filename);
  428. }
  429. };
  430. xhr.onprogress = function (progress) {
  431. if (progress.lengthComputable) {
  432. var percentComplete = Math.ceil(progress.loaded / progress.total * 1000) / 10;
  433. $dlButton2.text('下载中...' + percentComplete + '%');
  434. }
  435. };
  436. xhr.open('GET', fileurl);
  437. xhr.responseType = 'blob';
  438. xhr.send();
  439. });
  440. $(d).on('click', '.aq_lnk', function (e) {
  441. GM_openInTab('http://steamcommunity.com/sharedfiles/filedetails/?id=' + $(this).prev().prev().text());
  442. });
  443. }
  444. else if (d.domain.toLowerCase().indexOf('steamcommunity.com') > - 1) {
  445. curDomain = 0;
  446. var linkToAnyls = d.URL;
  447. var pattern = /steamcommunity.com\D*([0-9]{2,15})/i;
  448. var modID = pattern.exec(linkToAnyls);
  449. var $btnArea = $('.game_area_purchase_game');
  450. $btnArea.find('h1').remove();
  451. var $steamDlBtn = $('<a>', {
  452. 'class': 'btn_green_white_innerfade btn_border_2px btn_medium',
  453. id: 'aq-steam-dl-btn',
  454. style: 'position: static!important;margin-right: 10px'
  455. }).html('<span class="subscribeText" style="padding-left: 15px!important"><div class="subscribeOption subscribe selected">下载</div></span>');
  456. var $steamDlBtn2 = $steamDlBtn.clone().attr({
  457. id: 'aq-steam-dl-btn-2'
  458. }).find('div').text('重命名并下载(小型mod)').end();
  459. var $steamCopyBtn = $steamDlBtn.clone().attr({
  460. id: 'aq-steam-copy-btn'
  461. }).find('div').text('复制下载链接').end();
  462. $('#SubscribeItemBtn').children('div').hide().end().after($steamDlBtn).after($steamCopyBtn).after($steamDlBtn2);
  463. getDlUrl(modID[1]);
  464. }
  465. else {
  466. }
  467. }
  468. htmlParser();
  469. }) (window, document, undefined);