FSM 一键收藏 不喜欢 加上图片放大功能

Enhanced torrent page with gallery viewer, quick actions and keyboard shortcuts

  1. // ==UserScript==
  2. // @name FSM 一键收藏 不喜欢 加上图片放大功能
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4.0
  5. // @description Enhanced torrent page with gallery viewer, quick actions and keyboard shortcuts
  6. // @author You
  7. // @match https://fsm.name/Torrents/details*
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js
  10. // @resource FANCYBOX_CSS https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css
  11. // @grant GM_addStyle
  12. // @grant GM_getResourceText
  13. // @run-at document-end
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. try {
  21. const fancyboxCSS = GM_getResourceText('FANCYBOX_CSS');
  22. GM_addStyle(fancyboxCSS);
  23.  
  24. GM_addStyle(`
  25. .fancybox-bg {
  26. background: #000;
  27. }
  28. .fancybox-is-open .fancybox-bg {
  29. opacity: .9;
  30. }
  31. .fancybox-container {
  32. z-index: 999999 !important;
  33. }
  34.  
  35. .unified-gallery {
  36. display: grid;
  37. grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  38. gap: 10px;
  39. padding: 10px;
  40. margin: 15px 0;
  41. }
  42.  
  43. .unified-gallery a {
  44. display: block;
  45. position: relative;
  46. overflow: hidden;
  47. border-radius: 4px;
  48. background: #f5f5f5;
  49. }
  50.  
  51. .unified-gallery img {
  52. width: 100%;
  53. height: auto;
  54. display: block;
  55. transition: transform 0.3s ease;
  56. }
  57.  
  58. .unified-gallery a:hover img {
  59. transform: scale(1.05);
  60. }
  61. `);
  62. } catch (error) {
  63. console.error('Failed to add styles:', error);
  64. }
  65.  
  66. const $ = window.jQuery.noConflict(true);
  67.  
  68. function initFancybox() {
  69. try {
  70. $('[data-fancybox="gallery"]').fancybox({
  71. buttons: [
  72. "zoom",
  73. "slideShow",
  74. "fullScreen",
  75. "download",
  76. "thumbs",
  77. "close"
  78. ],
  79. loop: true,
  80. protect: false,
  81. animationEffect: "zoom",
  82. transitionEffect: "slide",
  83. thumbs: {
  84. autoStart: true,
  85. hideOnClose: true
  86. },
  87. mobile: {
  88. clickContent: function(current, event) {
  89. return current.type === "image" ? "toggleControls" : false;
  90. },
  91. clickSlide: function(current, event) {
  92. return current.type === "image" ? "toggleControls" : "close";
  93. },
  94. }
  95. });
  96. } catch (error) {
  97. console.error('Fancybox initialization failed:', error);
  98. }
  99. }
  100.  
  101. function reorganizeGallery(img) {
  102. try {
  103. console.log('进入reorganizeGallery')
  104. const contentArea = document.querySelector('.el-card__body');
  105. if (!contentArea) return;
  106.  
  107. const unifiedGallery = document.createElement('div');
  108. unifiedGallery.className = 'unified-gallery';
  109.  
  110. const allImages = [];
  111. const detailsImages = document.querySelectorAll('.ql-editor img');
  112. detailsImages.forEach(img => {
  113. if (img.src) allImages.push(img.src);
  114. });
  115.  
  116. const supplementImages = document.querySelectorAll('.screenshots .el-image img');
  117. supplementImages.forEach(img => {
  118. if (img.src) allImages.push(img.src);
  119. });
  120. console.log('allImages:',allImages)
  121. const uniqueImages = [...new Set(allImages)];
  122.  
  123. uniqueImages.forEach(src => {
  124. const link = document.createElement('a');
  125. link.href = src;
  126. link.setAttribute('data-fancybox', 'gallery');
  127.  
  128. const img = document.createElement('img');
  129. img.src = src;
  130. img.loading = 'lazy';
  131.  
  132. link.appendChild(img);
  133. unifiedGallery.appendChild(link);
  134. });
  135.  
  136. const headers = Array.from(document.querySelectorAll('h4')).filter(h =>
  137. h.textContent === '种子详情' || h.textContent === '补充信息'
  138. );
  139.  
  140. headers.forEach(header => {
  141. let next = header.nextElementSibling;
  142. while (next && next.tagName !== 'H4') {
  143. const temp = next.nextElementSibling;
  144. next.remove();
  145. next = temp;
  146. }
  147. header.remove();
  148. });
  149.  
  150. const originalScreenshots = document.querySelector('.screenshots');
  151. if (originalScreenshots) {
  152. originalScreenshots.remove();
  153. }
  154.  
  155. if (uniqueImages.length > 0) {
  156. contentArea.appendChild(unifiedGallery);
  157. setTimeout(initFancybox, 500);
  158. }
  159. } catch (error) {
  160. console.error('Failed to reorganize gallery:', error);
  161. }
  162. }
  163.  
  164. function voteTorrent(tid, value) {
  165. const authorization = localStorage.getItem('token')
  166. const deviceId = localStorage.getItem('DeviceId')
  167. const formData = new FormData();
  168. formData.append('tid', tid);
  169. formData.append('status', value);
  170.  
  171. fetch('/api/Torrents/voteTorrent', {
  172. method: 'POST',
  173. headers: {
  174. 'accept': 'application/json, text/plain, */*',
  175. 'authorization': authorization,
  176. 'deviceid': deviceId,
  177. 'origin': 'https://fsm.name',
  178. 'referer': window.location.href
  179. },
  180. body: formData,
  181. credentials: 'same-origin'
  182. })
  183. .then(response => {
  184. if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
  185. return response.json();
  186. })
  187. .then(res => {
  188. if (res?.success) {
  189. if (window.$notify) {
  190. window.$notify({
  191. message: '操作成功',
  192. type: 'success'
  193. });
  194. } else {
  195. window.close();
  196. }
  197. }
  198. })
  199. .catch(error => {
  200. if (window.$notify) {
  201. window.$notify({
  202. message: '操作失败',
  203. type: 'error'
  204. });
  205. } else {
  206. alert('操作失败');
  207. }
  208. });
  209. }
  210.  
  211. function addButtons() {
  212. const sideBlk = document.querySelector('.side-blk');
  213. if (!sideBlk) return;
  214.  
  215. const urlParams = new URLSearchParams(window.location.search);
  216. const tid = urlParams.get('tid');
  217. if (!tid) return;
  218.  
  219. const favoriteDiv = document.createElement('div');
  220. const favoriteBtn = document.createElement('button');
  221. favoriteBtn.className = 'el-button el-button--info el-button--large is-plain is-circle side-btn el-tooltip__trigger el-tooltip__trigger';
  222. favoriteBtn.style.display = 'block';
  223. favoriteBtn.innerHTML = `<i class="el-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="m512 747.84 228.16 119.936a6.4 6.4 0 0 0 9.28-6.72l-43.52-254.08 184.512-179.904a6.4 6.4 0 0 0-3.52-10.88l-255.104-37.12L517.76 147.904a6.4 6.4 0 0 0-11.52 0L392.192 379.072l-255.104 37.12a6.4 6.4 0 0 0-3.52 10.88L318.08 606.976l-43.584 254.08a6.4 6.4 0 0 0 9.28 6.72zM313.6 924.48a70.4 70.4 0 0 1-102.144-74.24l37.888-220.928L88.96 472.96A70.4 70.4 0 0 1 128 352.896l221.76-32.256 99.2-200.96a70.4 70.4 0 0 1 126.208 0l99.2 200.96 221.824 32.256a70.4 70.4 0 0 1 39.04 120.064L774.72 629.376l37.888 220.928a70.4 70.4 0 0 1-102.144 74.24L512 820.096l-198.4 104.32z"></path></svg></i>`;
  224. favoriteBtn.addEventListener('click', () => {
  225. voteTorrent(tid, 'VALUE');
  226. });
  227. favoriteDiv.appendChild(favoriteBtn);
  228.  
  229. const dislikeDiv = document.createElement('div');
  230. const dislikeBtn = document.createElement('button');
  231. dislikeBtn.className = 'el-button el-button--info el-button--large is-plain is-circle side-btn el-tooltip__trigger el-tooltip__trigger';
  232. dislikeBtn.style.display = 'block';
  233. dislikeBtn.innerHTML = `<i class="el-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32zm448-64v-64H416v64zM224 896h576V256H224zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32m192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32"></path></svg></i>`;
  234. dislikeBtn.addEventListener('click', () => {
  235. voteTorrent(tid, 'POINTLESS');
  236. });
  237. dislikeDiv.appendChild(dislikeBtn);
  238.  
  239. sideBlk.appendChild(favoriteDiv);
  240. sideBlk.appendChild(dislikeDiv);
  241.  
  242. window.dislikeButton = dislikeBtn;
  243. }
  244.  
  245. function addKeyboardShortcuts() {
  246. document.addEventListener('keydown', function(event) {
  247. if (event.key === 'x' &&
  248. !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) {
  249. window.dislikeButton?.click();
  250. }
  251. });
  252. }
  253.  
  254. let keyboardShortcutsAdded = false;
  255.  
  256. const galleryObserver = new MutationObserver((mutations, obs) => {
  257. const contentBody = document.querySelector('.el-card__body');
  258. const screenshots = document.querySelector('.ql-editor.ql-content-fix.img-beautify');
  259. console.log(contentBody , screenshots)
  260. if (contentBody && screenshots) {
  261. obs.disconnect();
  262. setTimeout(reorganizeGallery, 2000);
  263.  
  264. setTimeout(() => {
  265. if (!document.querySelector('[data-fancybox="gallery"]')) {
  266. reorganizeGallery();
  267. }
  268. }, 3000);
  269. }
  270. });
  271.  
  272. const buttonsObserver = new MutationObserver((mutations, obs) => {
  273. const sideBlk = document.querySelector('.side-blk');
  274. if (sideBlk) {
  275. obs.disconnect();
  276. addButtons();
  277.  
  278. if (!keyboardShortcutsAdded) {
  279. addKeyboardShortcuts();
  280. keyboardShortcutsAdded = true;
  281. }
  282. }
  283. });
  284.  
  285. galleryObserver.observe(document.body, {
  286. childList: true,
  287. subtree: true
  288. });
  289.  
  290. buttonsObserver.observe(document.body, {
  291. childList: true,
  292. subtree: true
  293. });
  294.  
  295. if (document.readyState === 'loading') {
  296. document.addEventListener('DOMContentLoaded', function() {
  297. addButtons();
  298. if (!keyboardShortcutsAdded) {
  299. addKeyboardShortcuts();
  300. keyboardShortcutsAdded = true;
  301. }
  302. });
  303. } else {
  304. addButtons();
  305. if (!keyboardShortcutsAdded) {
  306. addKeyboardShortcuts();
  307. keyboardShortcutsAdded = true;
  308. }
  309. }
  310. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址