4399-flash-downloader

一键下载 flash 游戏(swf)

目前为 2023-04-28 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 4399-flash-downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.1
  5. // @description 一键下载 flash 游戏(swf)
  6. // @author 2690874578@qq.com
  7. // @match https://www.4399.com/flash/*
  8. // @match https://s2.4399.com
  9. // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMiAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iMyIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzE3XzQpIi8+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTdfNCkiPgo8cGF0aCBkPSJNMyAzSDUuODIzMjNWNy41NDE3N0gxMS41MzU0VjNIMTQuMzU4NlYxNC45Nzk3SDExLjUzNTRWMTAuNDM4SDNWM1pNNS44ODg4OSAxOS45MTY1VjIxLjYyNzhIMTEuNjAxVjE5LjkxNjVINS44ODg4OVpNMjAuMzk5IDE5LjkxNjVWMjEuNjI3OEgyNi4xMTExVjE5LjkxNjVIMjAuMzk5Wk0xNy41NzU4IDE3LjAyMDNIMjlWMjlIMTcuNTc1OFYyNi4xNjk2SDI2LjExMTFWMjQuNDU4MkgxNy41NzU4VjE3LjAyMDNaTTMgMTcuMDIwM0gxNC40MjQyVjI5SDNWMjYuMTY5NkgxMS41MzU0VjI0LjQ1ODJIM1YxNy4wMjAzWk0xNy41NzU4IDNIMjlWMTQuOTc5N0gxNy41NzU4VjEyLjE0OTRIMjYuMTExMVYxMC40MzhIMTcuNTc1OFY3LjYwNzZIMjYuMTExMVY1Ljg5NjJIMTcuNTc1OFYzWiIgZmlsbD0id2hpdGUiLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzE3XzQiIHg9IjIiIHk9IjMiIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz4KPGZlT2Zmc2V0IGR4PSIxIiBkeT0iMiIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxIi8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC40IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTdfNCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18xN180IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMTdfNCIgeDE9IjMxLjY4IiB5MT0iMzEuMDQiIHgyPSIzLjkxMDA2ZS0wNyIgeTI9Ii0xNy42IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM1NkFGMEIiLz4KPHN0b3Agb2Zmc2V0PSIwLjk5MjcwOCIgc3RvcC1jb2xvcj0iIzUyQTUwRCIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==
  10. // @grant none
  11. // @run-at document-idle
  12. // @license GPL-3.0-only
  13. // ==/UserScript==
  14.  
  15.  
  16. (function() {
  17. /**
  18. * 脚本级全局常量
  19. */
  20.  
  21. BASE_URL = "https://s2.4399.com/4399swf";
  22. FLASH_ICON = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE0XzIpIj4KPHBhdGggZD0iTTI4LjI2NjcgMEgzLjczMzMzQzEuNzA2NjcgMCAwIDEuNzA2NjcgMCAzLjczMzMzVjI4LjI2NjdDMCAzMC4yOTMzIDEuNzA2NjcgMzIgMy43MzMzMyAzMkgyOC4yNjY3QzMwLjI5MzMgMzIgMzIgMzAuMjkzMyAzMiAyOC4yNjY3VjMuNzMzMzNDMzIgMS43MDY2NyAzMC4yOTMzIDAgMjguMjY2NyAwWk0yMy40NjY3IDEwLjEzMzNDMjMuNDY2NyAxMC40NTMzIDIzLjI1MzMgMTAuNjY2NyAyMi45MzMzIDEwLjY2NjdDMjAuMjY2NyAxMC42NjY3IDIwLjE2IDEwLjk4NjcgMTkuNzMzMyAxMi4xNkMxOS42MjY3IDEyLjM3MzMgMTkuNjI2NyAxMi41ODY3IDE5LjUyIDEyLjhIMjEuODY2N0MyMi4xODY3IDEyLjggMjIuNCAxMy4wMTMzIDIyLjQgMTMuMzMzM1YxNy42QzIyLjQgMTcuOTIgMjIuMTg2NyAxOC4xMzMzIDIxLjg2NjcgMTguMTMzM0gxOC4wMjY3QzE2Ljg1MzMgMjIuMjkzMyAxMi40OCAyNi42NjY3IDggMjYuNjY2N0M3LjY4IDI2LjY2NjcgNy40NjY2NyAyNi40NTMzIDcuNDY2NjcgMjYuMTMzM1YyMS44NjY3QzcuNDY2NjcgMjEuNTQ2NyA3LjY4IDIxLjMzMzMgOCAyMS4zMzMzQzExLjMwNjcgMjEuMzMzMyAxMi4yNjY3IDE4LjY2NjcgMTMuMzMzMyAxNS4yNTMzQzEzLjU0NjcgMTQuNzIgMTMuNjUzMyAxNC4yOTMzIDEzLjg2NjcgMTMuNzZDMTUuMjUzMyA5LjkyIDE2Ljg1MzMgNS4zMzMzMyAyMi45MzMzIDUuMzMzMzNDMjMuMjUzMyA1LjMzMzMzIDIzLjQ2NjcgNS41NDY2NyAyMy40NjY3IDUuODY2NjdWMTAuMTMzM1oiIGZpbGw9IiNEODFFMDYiLz4KPHBhdGggZD0iTTIxLjE3ODkgMzYuMDg0MkMxOS45MTU4IDM2LjA4NDIgMTguNjUyNiAzNS41Nzg5IDE3LjY0MjEgMzQuNTY4NEMxNS42MjEgMzIuNTQ3NCAxNS42MjEgMjkuMzg5NSAxNy42NDIxIDI3LjM2ODRMMjAuMjk0NyAyNC43MTU4TDIyLjA2MzIgMjYuNDg0MkwxOS40MTA1IDI5LjEzNjhDMTguNCAzMC4xNDc0IDE4LjQgMzEuNjYzMiAxOS40MTA1IDMyLjY3MzdDMjAuNDIxIDMzLjY4NDIgMjEuOTM2OCAzMy42ODQyIDIyLjk0NzQgMzIuNjczN0wyNi40ODQyIDI5LjEzNjhDMjYuOTg5NSAyOC42MzE2IDI3LjI0MjEgMjggMjcuMjQyMSAyNy4zNjg0QzI3LjI0MjEgMjYuNzM2OCAyNi45ODk1IDI2LjEwNTMgMjYuNjEwNSAyNS42TDI1LjIyMTEgMjQuMzM2OEwyNi45ODk1IDIyLjU2ODRMMjguMzc4OSAyMy45NTc5QzI5LjI2MzIgMjQuODQyMSAyOS43Njg0IDI2LjEwNTMgMjkuNzY4NCAyNy40OTQ3QzI5Ljc2ODQgMjguODg0MiAyOS4yNjMyIDMwLjE0NzQgMjguMjUyNiAzMS4wMzE2TDI0LjcxNTggMzQuNTY4NEMyMy44MzE2IDM1LjU3ODkgMjIuNDQyMSAzNi4wODQyIDIxLjE3ODkgMzYuMDg0MlpNMjUuMjIxMSAyOS42NDIxTDIzLjgzMTYgMjguMzc4OUMyMS44MTA1IDI2LjM1NzkgMjEuODEwNSAyMy4yIDIzLjgzMTYgMjEuMTc4OUwyNy4zNjg0IDE3LjY0MjFDMjkuMzg5NSAxNS42MjEgMzIuNTQ3NCAxNS42MjEgMzQuNTY4NCAxNy42NDIxQzM2LjU4OTUgMTkuNjYzMiAzNi41ODk1IDIyLjgyMSAzNC41Njg0IDI0Ljg0MjFMMzEuOTE1OCAyNy40OTQ3TDMwLjE0NzQgMjUuNzI2M0wzMi44IDIzLjA3MzdDMzMuODEwNSAyMi4wNjMyIDMzLjgxMDUgMjAuNTQ3NCAzMi44IDE5LjUzNjhDMzEuNzg5NSAxOC41MjYzIDMwLjE0NzQgMTguNTI2MyAyOS4yNjMyIDE5LjUzNjhMMjUuNiAyMi45NDc0QzI0LjU4OTUgMjMuOTU3OSAyNC41ODk1IDI1LjQ3MzcgMjUuNiAyNi40ODQyTDI2Ljk4OTUgMjcuODczN0wyNS4yMjExIDI5LjY0MjFaIiBmaWxsPSIjMjcyNjM2Ii8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTRfMiI+CjxyZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K`;
  23.  
  24.  
  25. /**
  26. * 脚本级公用函数和对象
  27. */
  28.  
  29. /**
  30. * 元素选择器
  31. * @param {string} selector 选择器
  32. * @returns {Array<HTMLElement>} 元素列表
  33. */
  34. function $(selector) {
  35. const self = this?.querySelectorAll ? this : document;
  36. return [...self.querySelectorAll(selector)];
  37. }
  38.  
  39.  
  40. /**
  41. * 安全元素选择器,直到元素存在时才返回元素列表,最多等待5秒
  42. * @param {string} selector 选择器
  43. * @returns {Promise<Array<HTMLElement>>} 元素列表
  44. */
  45. async function $$(selector) {
  46. const self = this?.querySelectorAll ? this : document;
  47.  
  48. for (let i = 0; i < 10; i++) {
  49. let elems = [...self.querySelectorAll(selector)];
  50. if (elems.length > 0) {
  51. return elems;
  52. }
  53. await new Promise(r => setTimeout(r, 500));
  54. }
  55. throw Error(`"${selector}" not found`);
  56. }
  57.  
  58.  
  59. const util = {
  60. Socket: class Socket {
  61. /**
  62. * 创建套接字对象
  63. * @param {Window} target 目标窗口
  64. */
  65. constructor(target) {
  66. if (!(target.window && (target === target.window))) {
  67. console.log(target);
  68. throw new Error(`target is not a [Window Object]`);
  69. }
  70. this.target = target;
  71. this.connected = false;
  72. this.listeners = new Set();
  73. }
  74. get [Symbol.toStringTag]() { return "Socket"; }
  75. /**
  76. * 向目标窗口发消息
  77. * @param {*} message
  78. */
  79. talk(message) {
  80. if (!this.target) {
  81. throw new TypeError(
  82. `socket.target is not a window: ${this.target}`
  83. );
  84. }
  85. this.target.postMessage(message, "*");
  86. }
  87. /**
  88. * 添加捕获型监听器,返回实际添加的监听器
  89. * @param {Function} listener (e: MessageEvent) => {...}
  90. * @param {boolean} once 是否在执行后自动销毁,默认 false;如为 true 则使用自动包装过的监听器
  91. * @returns {Function} listener
  92. */
  93. listen(listener, once=false) {
  94. if (this.listeners.has(listener)) {
  95. return;
  96. }
  97. let real_listener = listener;
  98. // 包装监听器
  99. if (once) {
  100. const self = this;
  101. function wrapped(e) {
  102. listener(e);
  103. self.not_listen(wrapped);
  104. }
  105. real_listener = wrapped;
  106. }
  107. // 添加监听器
  108. this.listeners.add(real_listener);
  109. window.addEventListener(
  110. "message", real_listener, true
  111. );
  112. return real_listener;
  113. }
  114. /**
  115. * 移除socket上的捕获型监听器
  116. * @param {Function} listener (e: MessageEvent) => {...}
  117. */
  118. not_listen(listener) {
  119. console.log(listener);
  120. console.log(
  121. "listener delete operation:",
  122. this.listeners.delete(listener)
  123. );
  124. window.removeEventListener("message", listener, true);
  125. }
  126. /**
  127. * 检查对方来信是否为pong消息
  128. * @param {MessageEvent} e
  129. * @param {Function} resolve
  130. */
  131. _on_pong(e, resolve) {
  132. // 收到pong消息
  133. if (e.data.pong) {
  134. this.connected = true;
  135. this.listeners.forEach(
  136. listener => listener.ping ? this.not_listen(listener) : 0
  137. );
  138. console.log("Client: Connected!\n" + new Date());
  139. resolve(this);
  140. }
  141. }
  142. /**
  143. * 向对方发送ping消息
  144. * @returns {Promise<Socket>}
  145. */
  146. _ping() {
  147. return new Promise((resolve, reject) => {
  148. // 绑定pong检查监听器
  149. const listener = this.listen(
  150. e => this._on_pong(e, resolve)
  151. );
  152. listener.ping = true;
  153. // 5分钟后超时
  154. setTimeout(
  155. () => reject(new Error(`Timeout Error during receiving pong (>5min)`)),
  156. 5 * 60 * 1000
  157. );
  158. // 发送ping消息
  159. this.talk({ ping: true });
  160. });
  161. }
  162. /**
  163. * 检查对方来信是否为ping消息
  164. * @param {MessageEvent} e
  165. * @param {Function} resolve
  166. */
  167. _on_ping(e, resolve) {
  168. // 收到ping消息
  169. if (e.data.ping) {
  170. this.target = e.source;
  171. this.connected = true;
  172. this.listeners.forEach(
  173. listener => listener.pong ? this.not_listen(listener) : 0
  174. );
  175. console.log("Server: Connected!\n" + new Date());
  176. // resolve 后期约状态无法回退
  177. // 但后续代码仍可执行
  178. resolve(this);
  179. // 回应pong消息
  180. this.talk({ pong: true });
  181. }
  182. }
  183. /**
  184. * 当对方来信是为ping消息时回应pong消息
  185. * @returns {Promise<Socket>}
  186. */
  187. _pong() {
  188. return new Promise(resolve => {
  189. // 绑定ping检查监听器
  190. const listener = this.listen(
  191. e => this._on_ping(e, resolve)
  192. );
  193. listener.pong = true;
  194. });
  195. }
  196. /**
  197. * 连接至目标窗口
  198. * @param {boolean} talk_first 是否先发送ping消息
  199. * @param {Window} target 目标窗口
  200. * @returns {Promise<Socket>}
  201. */
  202. connect(talk_first) {
  203. // 先发起握手
  204. if (talk_first) {
  205. return this._ping();
  206. }
  207. // 后发起握手
  208. return this._pong();
  209. }
  210. },
  211.  
  212. /**
  213. * 以指定原因弹窗提示并抛出错误
  214. * @param {string} reason
  215. */
  216. raise: function(reason) {
  217. alert(reason);
  218. throw new Error(reason);
  219. },
  220. /**
  221. * 返回一个包含计数器的迭代器, 其每次迭代值为 [index, value]
  222. * @param {Iterable} iterable
  223. * @returns
  224. */
  225. enumerate: function* (iterable) {
  226. let i = 0;
  227. for (let value of iterable) {
  228. yield [i++, value];
  229. }
  230. },
  231. /**
  232. * 同步的迭代若干可迭代对象
  233. * @param {...Iterable} iterables
  234. * @returns
  235. */
  236. zip: function* (...iterables) {
  237. // 强制转为迭代器
  238. const iterators = iterables.map(
  239. iterable => iterable[Symbol.iterator]()
  240. );
  241. // 逐次迭代
  242. while (true) {
  243. let [done, values] = base.getAllValus(iterators);
  244. if (done) {
  245. return;
  246. }
  247. if (values.length === 1) {
  248. yield values[0];
  249. } else {
  250. yield values;
  251. }
  252. }
  253. },
  254. /**
  255. * 返回指定范围整数生成器
  256. * @param {number} end 如果只提供 end, 则返回 [0, end)
  257. * @param {number} end2 如果同时提供 end2, 则返回 [end, end2)
  258. * @param {number} step 步长, 可以为负数,不能为 0
  259. * @returns
  260. */
  261. range: function*(end, end2=null, step=1) {
  262. // 参数合法性校验
  263. if (step === 0) {
  264. throw new RangeError("step can't be zero");
  265. }
  266. const len = end2 - end;
  267. if (end2 && len && step && (len * step < 0)) {
  268. throw new RangeError(`[${end}, ${end2}) with step ${step} is invalid`);
  269. }
  270. // 生成范围
  271. end2 = end2 === null ? 0 : end2;
  272. let [small, big] = [end, end2].sort((a, b) => a - b);
  273. // 开始迭代
  274. if (step > 0) {
  275. for (let i = small; i < big; i += step) {
  276. yield i;
  277. }
  278. } else {
  279. for (let i = big; i > small; i += step) {
  280. yield i;
  281. }
  282. };
  283. },
  284. /**
  285. * 复制text到剪贴板
  286. * @param {string} text
  287. * @returns
  288. */
  289. copy_text: function(text) {
  290. // 输出到控制台和剪贴板
  291. console.log(
  292. text.length > 20 ?
  293. text.slice(0, 21) + "..." : text
  294. );
  295. if (!navigator.clipboard) {
  296. base.oldCopy(text);
  297. return;
  298. };
  299. navigator.clipboard
  300. .writeText(text)
  301. .catch(_ => base.oldCopy(text));
  302. },
  303. /**
  304. * 复制媒体到剪贴板
  305. * @param {Blob} blob
  306. */
  307. copy: async function(blob) {
  308. const data = [new ClipboardItem({ [blob.type]: blob })];
  309. try {
  310. await navigator.clipboard.write(data);
  311. console.log(`${blob.type} 成功复制到剪贴板`);
  312. } catch (err) {
  313. console.error(err.name, err.message);
  314. }
  315. },
  316. /**
  317. * 创建并下载文件
  318. * @param {string} file_name 文件名
  319. * @param {ArrayBuffer | ArrayBufferView | Blob | string} content 内容
  320. * @param {string} type 媒体类型,需要符合 MIME 标准
  321. */
  322. save: function(file_name, content, type="") {
  323. const blob = new Blob(
  324. [content], { type }
  325. );
  326. const size = (blob.size / 1024).toFixed(1);
  327. console.log(`blob saved, size: ${size} kb, type: ${blob.type}`);
  328. const url = URL.createObjectURL(blob);
  329. const a = document.createElement("a");
  330. a.download = file_name || "未命名文件";
  331. a.href = url;
  332. a.click();
  333. URL.revokeObjectURL(url);
  334. },
  335. sleep: async function(delay_ms) {
  336. return new Promise(
  337. resolve => setTimeout(resolve, delay_ms)
  338. );
  339. },
  340. /**
  341. * 取得get参数key对应的value
  342. * @param {string} key
  343. * @returns {string} value
  344. */
  345. get_param: function(key) {
  346. return new URL(location.href).searchParams.get(key);
  347. },
  348. /**
  349. * 等待直到函数返回true
  350. * @param {Function} is_ok 判断条件达成与否的函数
  351. * @param {number} timeout 最大等待秒数, 默认5000毫秒
  352. */
  353. wait_until: async function(is_ok, timeout=5000) {
  354. const gap = 200;
  355. let chances = parseInt(timeout / gap);
  356. chances = chances < 1 ? 1 : chances;
  357. while (! await is_ok()) {
  358. await this.sleep(200);
  359. chances -= 1;
  360. if (!chances) {
  361. break;
  362. }
  363. }
  364. },
  365. /**
  366. * 用try移除元素
  367. * @param {HTMLElement} element 要移除的元素
  368. */
  369. remove: function(element) {
  370. try {
  371. element.remove();
  372. } catch (e) {}
  373. },
  374. /**
  375. * 等待全部任务落定后返回值的列表
  376. * @param {Iterable<Promise>} tasks
  377. * @returns {Promise<Array>} values
  378. */
  379. gather: async function(tasks) {
  380. const results = await Promise.allSettled(tasks);
  381. return results
  382. .filter(result => result.value)
  383. .map(result => result.value);
  384. },
  385. /**
  386. * 使用xhr异步GET请求目标url,返回响应体blob
  387. * @param {string} url
  388. * @returns {Promise<Blob>} blob
  389. */
  390. xhr_get_blob: async function(url) {
  391. const xhr = new XMLHttpRequest();
  392. xhr.open("GET", url);
  393. xhr.responseType = "blob";
  394. return new Promise((resolve, reject) => {
  395. xhr.onload = () => {
  396. const code = xhr.status;
  397. if (code >= 200 && code <= 299) {
  398. resolve(xhr.response);
  399. }
  400. else {
  401. reject(new Error(`Network Error: ${code}`));
  402. }
  403. }
  404. xhr.send();
  405. });
  406. },
  407. /**
  408. * 加载CDN脚本
  409. * @param {string} url
  410. */
  411. load_web_script: async function(url) {
  412. try {
  413. // xhr+eval方式
  414. Function(
  415. await (await this.xhr_get_blob(url)).text()
  416. )();
  417. } catch(e) {
  418. console.error(e);
  419. // 嵌入<script>方式
  420. const script = document.createElement("script");
  421. script.src = url;
  422. document.body.append(script);
  423. }
  424. },
  425. };
  426.  
  427. /**
  428. * 域名级主函数
  429. */
  430.  
  431.  
  432. /**
  433. * 启动下载 flash 游戏文件
  434. */
  435. function dl_flash() {
  436. /**
  437. * 域名级全局变量
  438. */
  439.  
  440. let sock;
  441.  
  442.  
  443. async function send_url() {
  444. const title = $(".name a")[0].textContent.trim() || "flash游戏";
  445. const path = window._strGamePath;
  446.  
  447. if (!path) util.raise(
  448. "_strGamePath 不存在,找不到游戏文件路径"
  449. );
  450. if (!path.endsWith(".swf")) util.raise(
  451. `当前游戏不是 flash 游戏。\n游戏路径为:${path}`
  452. );
  453.  
  454. const id = "flash-dl-src";
  455. let iframe = $(`#${id}`)[0];
  456.  
  457. if (!iframe) {
  458. iframe = document.createElement("iframe");
  459. iframe.id = id;
  460. iframe.src = "https://s2.4399.com";
  461. document.body.append(iframe);
  462. sock = new util.Socket(iframe.contentWindow);
  463. await sock.connect(false);
  464. }
  465. sock.talk({
  466. flash_dl: true,
  467. url: BASE_URL + path,
  468. title,
  469. });
  470. }
  471.  
  472. function add_style() {
  473. const style = `
  474. <style>
  475. #flash-dl-btn {
  476. text-align: center;
  477. background: url("${FLASH_ICON}");
  478. background-repeat: no-repeat;
  479. background-position: top;
  480. width: 40px;
  481. padding-top: 30px;
  482. margin: 0 10px;
  483. float: left;
  484. display: inline;
  485. cursor: pointer;
  486. }
  487.  
  488. #flash-dl-src {
  489. display: none;
  490. }
  491. <style>
  492. `;
  493. document.head.insertAdjacentHTML(
  494. "beforeend", style
  495. );
  496. }
  497.  
  498. async function add_dl_btn() {
  499. const box = (await $$("#uplayer .fr"))[0];
  500.  
  501. // 修改误导性的下载按钮文本(下载4399游戏盒子)
  502. $("#down_a")[0].textContent = "盒子";
  503. // 新按钮
  504. const btn = document.createElement("a");
  505. btn.id = "flash-dl-btn";
  506. btn.textContent = "下载";
  507. btn.onfocus = () => btn.blur();
  508. btn.onclick = send_url;
  509. box.insertAdjacentElement("afterbegin", btn);
  510. }
  511.  
  512. (() => {
  513. console.log("enter: dl_flash");
  514. add_style();
  515. add_dl_btn();
  516. })();
  517. }
  518.  
  519. /**
  520. * 执行下载 flash 游戏文件
  521. */
  522. function dl_flash_in_origin() {
  523. /**
  524. * @param {MessageEvent} e
  525. */
  526. async function on_msg(e) {
  527. if (!e.data.flash_dl) return;
  528.  
  529. const { url, title } = e.data;
  530. const resp = await fetch(url, {
  531. headers: {
  532. "Host": "szhong.4399.com",
  533. "X-Requested-With": "ShockwaveFlash/34.0.0.282",
  534. }
  535. });
  536. if (!resp.ok) util.raise(
  537. `游戏下载失败,错误代码:${resp.status},原因:${resp.statusText}`
  538. );
  539.  
  540. const blob = await resp.blob();
  541. util.save(title, blob, "application/x-shockwave-flash");
  542. }
  543.  
  544. (() => {
  545. console.log("enter: dl_flash_in_origin")
  546. if (window.top === window) return;
  547.  
  548. const sock = new util.Socket(window.top);
  549. sock.listen(on_msg);
  550. sock.connect(true);
  551. })();
  552. }
  553.  
  554.  
  555. /**
  556. * 路由函数,脚本主函数入口
  557. */
  558. function route() {
  559. console.log("enter: route");
  560.  
  561. const host = location.hostname;
  562. switch (host) {
  563. case "www.4399.com":
  564. dl_flash();
  565. break;
  566.  
  567. case "s2.4399.com":
  568. dl_flash_in_origin();
  569. break;
  570. default:
  571. console.log(`不受支持的域名:${host}`);
  572. break;
  573. }
  574. }
  575.  
  576.  
  577. setTimeout(route, 500);
  578.  
  579. /**
  580. * 更新日志
  581. * ---
  582. * 更新日期:2023/4/28
  583. * - 完成第一版 4399 flash 文件下载脚本
  584. */
  585. })();