script.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. class DownloadTool {
  2. constructor() {
  3. this.form = document.getElementById('downloadForm');
  4. this.output = document.getElementById('output');
  5. this.loadUrlsBtn = document.getElementById('loadUrls');
  6. this.urlListTextarea = document.getElementById('urlList');
  7. this.downloadUrlBtn = document.getElementById('downloadUrl');
  8. this.cleanFilesBtn = document.getElementById('cleanFiles');
  9. this.downloadImageBtn = document.getElementById('downloadImage');
  10. this.checkIncompleteBtn = document.getElementById('checkIncomplete');
  11. this.clearOutputBtn = document.getElementById('clearOutput');
  12. this.proxySelect = document.getElementById('proxy');
  13. this.websocket = null;
  14. this.isConnected = false;
  15. this.initEvents();
  16. this.connectWebSocket();
  17. }
  18. initEvents() {
  19. // 读取URL按钮
  20. this.loadUrlsBtn.addEventListener('click', () => {
  21. this.loadTargetUrls();
  22. });
  23. // 下载URL按钮
  24. this.downloadUrlBtn.addEventListener('click', () => {
  25. this.downloadUrls()
  26. });
  27. // 下载图片按钮
  28. this.downloadImageBtn.addEventListener('click', () => {
  29. this.downloadImages()
  30. });
  31. // 检查未完成按钮
  32. this.checkIncompleteBtn.addEventListener('click', () => {
  33. this.checkIncomplete();
  34. });
  35. // 清理文件按钮
  36. this.cleanFilesBtn.addEventListener('click', () => {
  37. this.cleanFiles();
  38. });
  39. // 清除输出按钮
  40. this.clearOutputBtn.addEventListener('click', () => {
  41. this.clearOutput();
  42. });
  43. }
  44. connectWebSocket() {
  45. try {
  46. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  47. const wsUrl = `${protocol}//${window.location.host}/ws`;
  48. this.websocket = new WebSocket(wsUrl);
  49. this.websocket.onopen = () => {
  50. this.isConnected = true;
  51. this.showOutput('WebSocket连接已建立,可以接收实时日志', 'success');
  52. console.log('WebSocket连接已建立');
  53. };
  54. this.websocket.onmessage = (event) => {
  55. try {
  56. const logEntry = JSON.parse(event.data);
  57. this.appendRealtimeLog(logEntry);
  58. } catch (e) {
  59. console.error('解析WebSocket消息失败:', e);
  60. }
  61. };
  62. this.websocket.onclose = () => {
  63. this.isConnected = false;
  64. this.showOutput('WebSocket连接已断开,正在尝试重连...', 'error');
  65. console.log('WebSocket连接已断开');
  66. // 5秒后尝试重连
  67. setTimeout(() => this.connectWebSocket(), 5000);
  68. };
  69. this.websocket.onerror = (error) => {
  70. console.error('WebSocket错误:', error);
  71. this.showOutput('WebSocket连接错误', 'error');
  72. };
  73. } catch (error) {
  74. console.error('创建WebSocket连接失败:', error);
  75. this.showOutput('WebSocket连接失败', 'error');
  76. }
  77. }
  78. appendRealtimeLog(logEntry) {
  79. const timestamp = logEntry.time || new Date().toLocaleTimeString();
  80. const level = logEntry.level || 'INFO';
  81. const source = logEntry.source || 'system';
  82. const message = logEntry.message || '';
  83. const logLine = `[${timestamp}] [${level}] [${source}] ${message}`;
  84. // 追加到输出框
  85. if (this.output.textContent) {
  86. this.output.textContent += '\n' + logLine;
  87. } else {
  88. this.output.textContent = logLine;
  89. }
  90. // 自动滚动到底部
  91. this.output.scrollTop = this.output.scrollHeight;
  92. // 根据日志级别设置样式
  93. if (level === 'ERROR') {
  94. this.output.classList.add('error');
  95. } else if (level === 'SUCCESS') {
  96. this.output.classList.add('success');
  97. } else {
  98. this.output.classList.remove('error', 'success');
  99. }
  100. }
  101. async loadTargetUrls() {
  102. try {
  103. this.setLoading(true);
  104. this.showOutput('正在读取 targets.txt...', 'info');
  105. const response = await fetch('/load_urls', {
  106. method: 'POST'
  107. });
  108. const result = await response.json();
  109. if (result.success) {
  110. // 在URL列表文本框中显示读取的URL
  111. this.urlListTextarea.value = result.urls.join('\n');
  112. this.showOutput(`成功读取 ${result.urls.length} 个URL\n\nURL列表:\n${result.urls.join('\n')}`, 'success');
  113. } else {
  114. this.showOutput(`读取失败: ${result.message}`, 'error');
  115. }
  116. } catch (error) {
  117. this.showOutput(`读取URL时出错: ${error.message}`, 'error');
  118. } finally {
  119. this.setLoading(false);
  120. }
  121. }
  122. async clearOutput() {
  123. try {
  124. const response = await fetch('/clear', {
  125. method: 'POST'
  126. });
  127. const result = await response.json();
  128. if (result.success) {
  129. this.showOutput('', 'success');
  130. this.urlListTextarea.value = ''; // 同时清空URL列表
  131. }
  132. } catch (error) {
  133. this.showOutput(`清除失败: ${error.message}`, 'error');
  134. }
  135. }
  136. async downloadUrls() {
  137. try {
  138. const proxy = this.proxySelect.value;
  139. this.showOutput(`正在抓取画廊链接...\n代理: ${proxy}\n\n注意:此操作可能需要较长时间,请耐心等待...`, 'info');
  140. // 使用setTimeout确保UI不被阻塞
  141. setTimeout(async () => {
  142. try {
  143. const res = await fetch('/download_urls', {
  144. method: 'POST',
  145. headers: { 'Content-Type': 'application/json' },
  146. body: JSON.stringify({ proxy })
  147. });
  148. const data = await res.json();
  149. this.showOutput(data.message, data.success ? 'success' : 'error');
  150. } catch (error) {
  151. this.showOutput(`抓取画廊链接时出错: ${error.message}`, 'error');
  152. }
  153. }, 100);
  154. } catch (error) {
  155. this.showOutput(`抓取画廊链接时出错: ${error.message}`, 'error');
  156. }
  157. }
  158. async downloadImages() {
  159. try {
  160. const proxy = this.proxySelect.value;
  161. this.showOutput(`正在下载图片...\n代理: ${proxy}\n\n注意:此操作可能需要较长时间,请耐心等待...`, 'info');
  162. // 使用setTimeout确保UI不被阻塞
  163. setTimeout(async () => {
  164. try {
  165. const res = await fetch('/download_images', {
  166. method: 'POST',
  167. headers: { 'Content-Type': 'application/json' },
  168. body: JSON.stringify({ proxy })
  169. });
  170. const data = await res.json();
  171. this.showOutput(data.message, data.success ? 'success' : 'error');
  172. } catch (error) {
  173. this.showOutput(`下载图片时出错: ${error.message}`, 'error');
  174. }
  175. }, 100);
  176. } catch (error) {
  177. this.showOutput(`下载图片时出错: ${error.message}`, 'error');
  178. }
  179. }
  180. async checkIncomplete() {
  181. try {
  182. this.setLoading(true);
  183. this.showOutput('正在检查未完成文件...', 'info');
  184. const response = await fetch('/check_incomplete', {
  185. method: 'POST'
  186. });
  187. const result = await response.json();
  188. if (result.success) {
  189. let message = `检查完成!\n\n`;
  190. message += `${result.data}`;
  191. this.showOutput(message, 'success');
  192. } else {
  193. this.showOutput(`检查失败: ${result.message}`, 'error');
  194. }
  195. } catch (error) {
  196. this.showOutput(`检查未完成文件时出错: ${error.message}`, 'error');
  197. } finally {
  198. this.setLoading(false);
  199. }
  200. }
  201. async cleanFiles() {
  202. try {
  203. this.setLoading(true);
  204. this.showOutput('正在清理日志和JSON文件...', 'info');
  205. const response = await fetch('/clean_files', {
  206. method: 'POST'
  207. });
  208. const result = await response.json();
  209. if (result.success) {
  210. let message = `清理完成!成功删除 ${result.deleted_count} 个文件\n\n`;
  211. if (result.deleted_files && result.deleted_files.length > 0) {
  212. message += "已删除的文件:\n" + result.deleted_files.join('\n');
  213. }
  214. this.showOutput(message, 'success');
  215. } else {
  216. let message = `清理完成,但有 ${result.error_count} 个文件删除失败\n\n`;
  217. if (result.deleted_files && result.deleted_files.length > 0) {
  218. message += "已删除的文件:\n" + result.deleted_files.join('\n') + '\n\n';
  219. }
  220. if (result.error_files && result.error_files.length > 0) {
  221. message += "删除失败的文件:\n" + result.error_files.join('\n');
  222. }
  223. this.showOutput(message, 'error');
  224. }
  225. } catch (error) {
  226. this.showOutput(`清理文件时出错: ${error.message}`, 'error');
  227. } finally {
  228. this.setLoading(false);
  229. }
  230. }
  231. showOutput(message, type = '') {
  232. this.output.textContent = message;
  233. this.output.className = 'output-area';
  234. if (type) {
  235. this.output.classList.add(type);
  236. }
  237. // 自动滚动到底部
  238. this.output.scrollTop = this.output.scrollHeight;
  239. }
  240. setLoading(loading) {
  241. const buttons = this.form.querySelectorAll('button');
  242. buttons.forEach(button => {
  243. button.disabled = loading;
  244. });
  245. if (loading) {
  246. document.body.classList.add('loading');
  247. } else {
  248. document.body.classList.remove('loading');
  249. }
  250. }
  251. }
  252. // 初始化应用
  253. document.addEventListener('DOMContentLoaded', () => {
  254. new DownloadTool();
  255. });