115 Rename for CN

免VPN 番号重命名 free version(Query and modify filenames based on existing filename "番号”, includes detailed notification feature)

  1. // ==UserScript==
  2. // @name 115 Rename for CN
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @description 免VPN 番号重命名 free version(Query and modify filenames based on existing filename "番号”, includes detailed notification feature)
  6. // @author no_one
  7. // @include https://115.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_notification
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // HTML for the rename buttons, only keeping javhoo related buttons
  17. const renameListHTML = `
  18. <li id="rename_list">
  19. <a id="rename_all_javhoo" class="mark" href="javascript:;">Rename</a>
  20. <a id="rename_all_javhoo_date" class="mark" href="javascript:;">Rename with Date</a>
  21. </li>
  22. `;
  23.  
  24. // Base URLs for javhoo
  25. const JAVHOO_BASE = "https://www.javhoo.top/";
  26. const JAVHOO_SEARCH = `${JAVHOO_BASE}av/`;
  27. const JAVHOO_UNCENSORED_SEARCH = `${JAVHOO_BASE}uncensored/av/`;
  28.  
  29. // Timer ID
  30. let intervalId = null;
  31.  
  32. // Constants
  33. const MAX_RETRIES = 3;
  34. const MAX_CONCURRENT_REQUESTS = 5;
  35. const NOTIFICATION_DISPLAY_TIME = 4000; // 4 seconds
  36.  
  37. // State variables
  38. let activeRequests = 0;
  39. const requestQueue = [];
  40.  
  41. const progressData = {
  42. total: 0,
  43. processed: 0,
  44. success: 0,
  45. failed: 0
  46. };
  47.  
  48. const notificationQueue = [];
  49. let isNotificationShowing = false;
  50.  
  51. /**
  52. * Initialize the script
  53. */
  54. function init() {
  55. intervalId = setInterval(addRenameButtons, 1000);
  56. injectProgressBar();
  57. requestNotificationPermission();
  58. }
  59.  
  60. /**
  61. * Request notification permission and test
  62. */
  63. function requestNotificationPermission() {
  64. if (Notification.permission === "default") {
  65. Notification.requestPermission().then(permission => {
  66. if (permission === "granted") {
  67. new Notification("115Rename", { body: "Notification permission granted." });
  68. } else {
  69. console.log("Notification permission denied.");
  70. }
  71. });
  72. } else if (Notification.permission === "granted") {
  73. new Notification("115Rename", { body: "Script loaded." });
  74. }
  75. }
  76.  
  77. /**
  78. * Periodically check and add rename buttons
  79. */
  80. function addRenameButtons() {
  81. const openDir = $("div#js_float_content li[val='open_dir']");
  82. if (openDir.length !== 0 && $("li#rename_list").length === 0) {
  83. openDir.before(renameListHTML);
  84. bindButtonEvents();
  85. console.log("Rename buttons added");
  86. clearInterval(intervalId);
  87. }
  88. }
  89.  
  90. /**
  91. * Bind click events to buttons
  92. */
  93. function bindButtonEvents() {
  94. $("#rename_all_javhoo").on("click", () => {
  95. rename(rename_javhoo, false);
  96. });
  97. $("#rename_all_javhoo_date").on("click", () => {
  98. rename(rename_javhoo, true);
  99. });
  100. }
  101.  
  102. /**
  103. * Execute rename operation
  104. * @param {Function} call Callback function
  105. * @param {Boolean} addDate Whether to add date
  106. */
  107. function rename(call, addDate) {
  108. const selectedItems = $("iframe[rel='wangpan']")
  109. .contents()
  110. .find("li.selected");
  111.  
  112. if (selectedItems.length === 0) {
  113. enqueueNotification("Please select files to rename.", "error");
  114. return;
  115. }
  116.  
  117. // Reset progress data
  118. progressData.total = selectedItems.length;
  119. progressData.processed = 0;
  120. progressData.success = 0;
  121. progressData.failed = 0;
  122. updateProgressBar();
  123.  
  124. // Show start notification
  125. enqueueNotification(`Starting to rename ${progressData.total} files...`, "info");
  126.  
  127. selectedItems.each(function () {
  128. const $item = $(this);
  129. const fileName = $item.attr("title");
  130. const fileType = $item.attr("file_type");
  131. let fid, suffix;
  132.  
  133. if (fileType === "0") {
  134. fid = $item.attr("cate_id");
  135. } else {
  136. fid = $item.attr("file_id");
  137. const lastDot = fileName.lastIndexOf('.');
  138. if (lastDot !== -1) {
  139. suffix = fileName.substring(lastDot);
  140. }
  141. }
  142.  
  143. if (fid && fileName) {
  144. const fh = getVideoCode(fileName);
  145. if (fh) {
  146. const chineseCaptions = checkChineseCaptions(fh, fileName);
  147. enqueueRequest(() => call(fid, fh, suffix, chineseCaptions, addDate, $item));
  148. } else {
  149. progressData.processed++;
  150. progressData.failed++;
  151. updateProgressBar();
  152. enqueueNotification(`${fileName}: No code extracted`, "error");
  153. }
  154. }
  155. });
  156.  
  157. processQueue();
  158. }
  159.  
  160. /**
  161. * Add request to queue and process
  162. * @param {Function} requestFn Request function
  163. */
  164. function enqueueRequest(requestFn) {
  165. requestQueue.push(requestFn);
  166. console.log(`Request added to queue. Current queue length: ${requestQueue.length}`);
  167. }
  168.  
  169. /**
  170. * Process the request queue with concurrency limit
  171. */
  172. function processQueue() {
  173. while (activeRequests < MAX_CONCURRENT_REQUESTS && requestQueue.length > 0) {
  174. const requestFn = requestQueue.shift();
  175. activeRequests++;
  176. console.log(`Processing request from queue. Active requests: ${activeRequests}`);
  177. requestFn().finally(() => {
  178. activeRequests--;
  179. console.log(`Request completed. Active requests: ${activeRequests}`);
  180. processQueue();
  181. });
  182. }
  183. }
  184.  
  185. /**
  186. * Query javhoo and rename
  187. * @param {String} fid File ID
  188. * @param {String} fh Code
  189. * @param {String} suffix File suffix
  190. * @param {Boolean} chineseCaptions Whether it has Chinese captions
  191. * @param {Boolean} addDate Whether to add date
  192. * @param {jQuery} $item jQuery object of the file
  193. * @returns {Promise}
  194. */
  195. function rename_javhoo(fid, fh, suffix, chineseCaptions, addDate, $item) {
  196. const url = JAVHOO_SEARCH;
  197. return requestJavhoo(fid, fh, suffix, chineseCaptions, addDate, url, 0, $item);
  198. }
  199.  
  200. /**
  201. * Request javhoo and handle rename
  202. * @param {String} fid File ID
  203. * @param {String} fh Code
  204. * @param {String} suffix File suffix
  205. * @param {Boolean} chineseCaptions Whether it has Chinese captions
  206. * @param {Boolean} addDate Whether to add date
  207. * @param {String} url Request URL
  208. * @param {Number} retryCount Current retry count
  209. * @param {jQuery} $item jQuery object of the file
  210. * @returns {Promise}
  211. */
  212. function requestJavhoo(fid, fh, suffix, chineseCaptions, addDate, url, retryCount, $item) {
  213. return new Promise((resolve, reject) => {
  214. GM_xmlhttpRequest({
  215. method: "GET",
  216. url: `${url}${fh}`,
  217. headers: {
  218. "User-Agent": navigator.userAgent,
  219. "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  220. },
  221. onload: (xhr) => {
  222. if (xhr.status !== 200) {
  223. console.warn(`Request failed, status code: ${xhr.status}`);
  224. handleRetryOrFail(`HTTP ${xhr.status}`, fid, fh, suffix, chineseCaptions, addDate, url, retryCount, $item, resolve, reject);
  225. return;
  226. }
  227.  
  228. const parser = new DOMParser();
  229. const doc = parser.parseFromString(xhr.responseText, "text/html");
  230.  
  231. const titleElement = doc.querySelector("header.article-header h1.article-title");
  232. const title = titleElement ? titleElement.textContent.trim() : null;
  233.  
  234. console.log(`Extracted title: "${title}"`);
  235.  
  236. if (title) {
  237. let newName = buildNewName(fh, suffix, chineseCaptions, title);
  238.  
  239. if (addDate) {
  240. const currentDate = new Date().toISOString().split('T')[0];
  241. newName = `${currentDate}_${newName}`;
  242. }
  243.  
  244. if (newName) {
  245. send_115(fid, newName, fh)
  246. .then(() => {
  247. progressData.processed++;
  248. progressData.success++;
  249. updateProgressBar();
  250. enqueueNotification(`${fh}: Rename successful`, "success");
  251. updateDOM($item, newName);
  252. resolve();
  253. })
  254. .catch((error) => {
  255. progressData.processed++;
  256. progressData.failed++;
  257. updateProgressBar();
  258. enqueueNotification(`${fh}: Rename failed - ${error}`, "error");
  259. reject(error);
  260. });
  261. } else {
  262. progressData.processed++;
  263. progressData.failed++;
  264. updateProgressBar();
  265. enqueueNotification(`${fh}: Failed to generate new name`, "error");
  266. reject("Failed to generate new name");
  267. }
  268. } else if (url !== JAVHOO_UNCENSORED_SEARCH && retryCount < MAX_RETRIES) {
  269. console.warn(`Attempting uncensored search: ${JAVHOO_UNCENSORED_SEARCH}${fh}`);
  270. requestJavhoo(fid, fh, suffix, chineseCaptions, addDate, JAVHOO_UNCENSORED_SEARCH, retryCount + 1, $item)
  271. .then(resolve)
  272. .catch(reject);
  273. } else {
  274. progressData.processed++;
  275. progressData.failed++;
  276. updateProgressBar();
  277. enqueueNotification(`${fh}: Title not found or error occurred`, "error");
  278. reject("Title not found or error occurred");
  279. }
  280. },
  281. onerror: () => {
  282. console.warn(`Request failed: ${url}${fh}`);
  283. handleRetryOrFail("Network error", fid, fh, suffix, chineseCaptions, addDate, url, retryCount, $item, resolve, reject);
  284. }
  285. });
  286. });
  287. }
  288.  
  289. /**
  290. * Handle retry or fail
  291. * @param {String} errorMsg Error message
  292. * @param {String} fid File ID
  293. * @param {String} fh Code
  294. * @param {String} suffix File suffix
  295. * @param {Boolean} chineseCaptions Whether it has Chinese captions
  296. * @param {Boolean} addDate Whether to add date
  297. * @param {String} url Request URL
  298. * @param {Number} retryCount Current retry count
  299. * @param {jQuery} $item jQuery object of the file
  300. * @param {Function} resolve Promise resolve function
  301. * @param {Function} reject Promise reject function
  302. */
  303. function handleRetryOrFail(errorMsg, fid, fh, suffix, chineseCaptions, addDate, url, retryCount, $item, resolve, reject) {
  304. if (retryCount < MAX_RETRIES) {
  305. console.warn(`Request failed (${errorMsg}), retrying (${retryCount + 1}/${MAX_RETRIES}): ${url}${fh}`);
  306. const newUrl = url === JAVHOO_SEARCH ? JAVHOO_UNCENSORED_SEARCH : url;
  307. requestJavhoo(fid, fh, suffix, chineseCaptions, addDate, newUrl, retryCount + 1, $item)
  308. .then(resolve)
  309. .catch(reject);
  310. } else {
  311. progressData.processed++;
  312. progressData.failed++;
  313. updateProgressBar();
  314. enqueueNotification(`${fh}: ${errorMsg}`, "error");
  315. reject(errorMsg);
  316. }
  317. }
  318.  
  319. /**
  320. * Build new file name
  321. * @param {String} fh Code
  322. * @param {String} suffix File suffix
  323. * @param {Boolean} chineseCaptions Whether it has Chinese captions
  324. * @param {String} title Title
  325. * @returns {String} New name
  326. */
  327. function buildNewName(fh, suffix, chineseCaptions, title) {
  328. let newName = '';
  329.  
  330. if (title.startsWith(fh)) {
  331. newName = title;
  332. } else {
  333. newName = `${fh}`;
  334. if (chineseCaptions) {
  335. newName += "【中文字幕】";
  336. }
  337. newName += ` ${title}`;
  338. }
  339.  
  340. if (suffix) {
  341. newName += suffix;
  342. }
  343.  
  344. return newName;
  345. }
  346.  
  347. /**
  348. * Send rename request to 115.com
  349. * @param {String} fid File ID
  350. * @param {String} name New file name
  351. * @param {String} fh Code
  352. * @returns {Promise} Request promise
  353. */
  354. function send_115(fid, name, fh) {
  355. return new Promise((resolve, reject) => {
  356. const standardizedName = stringStandard(name);
  357. $.post("https://webapi.115.com/files/edit", {
  358. fid: fid,
  359. file_name: standardizedName
  360. })
  361. .done((data) => {
  362. console.log("send_115 response data:", data);
  363. try {
  364. const result = typeof data === "string" ? JSON.parse(data) : data;
  365. if (result.state) {
  366. resolve();
  367. } else {
  368. const errorMsg = unescape(result.error.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
  369. reject(errorMsg);
  370. }
  371. } catch (e) {
  372. console.error("Failed to parse response:", e);
  373. reject("Failed to parse response");
  374. }
  375. })
  376. .fail(() => {
  377. reject("Network error");
  378. });
  379. });
  380. }
  381.  
  382. /**
  383. * Add notification to queue and process
  384. * @param {String} message Notification message
  385. * @param {String} type Notification type ('success', 'error', 'info')
  386. */
  387. function enqueueNotification(message, type = "info") {
  388. notificationQueue.push({ message, type });
  389. console.log(`Notification added to queue: ${message} Type: ${type}`);
  390. processNotificationQueue();
  391. }
  392.  
  393. /**
  394. * Process notification queue to ensure one notification at a time
  395. */
  396. function processNotificationQueue() {
  397. if (isNotificationShowing || notificationQueue.length === 0) {
  398. return;
  399. }
  400.  
  401. isNotificationShowing = true;
  402. const { message, type } = notificationQueue.shift();
  403. console.log(`Displaying notification: ${message} Type: ${type}`);
  404.  
  405. // Use browser's Notification API
  406. if (Notification.permission === "granted") {
  407. let notification = new Notification("115Rename", { body: message });
  408.  
  409. notification.onclose = () => {
  410. isNotificationShowing = false;
  411. processNotificationQueue();
  412. };
  413. } else if (Notification.permission !== "denied") {
  414. Notification.requestPermission().then(permission => {
  415. if (permission === "granted") {
  416. let notification = new Notification("115Rename", { body: message });
  417.  
  418. notification.onclose = () => {
  419. isNotificationShowing = false;
  420. processNotificationQueue();
  421. };
  422. } else {
  423. // Fallback to alert if permission denied
  424. alert(message);
  425. isNotificationShowing = false;
  426. processNotificationQueue();
  427. }
  428. });
  429. } else {
  430. // Fallback to alert if permission denied
  431. alert(message);
  432. isNotificationShowing = false;
  433. processNotificationQueue();
  434. }
  435. }
  436.  
  437. /**
  438. * Standardize file name by removing or replacing invalid characters
  439. * @param {String} name Original file name
  440. * @returns {String} Standardized file name
  441. */
  442. function stringStandard(name) {
  443. return name.replace(/\\/g, "")
  444. .replace(/\//g, " ")
  445. .replace(/:/g, " ")
  446. .replace(/\?/g, " ")
  447. .replace(/"/g, " ")
  448. .replace(/</g, " ")
  449. .replace(/>/g, " ")
  450. .replace(/\|/g, "")
  451. .replace(/\*/g, " ");
  452. }
  453.  
  454. /**
  455. * Check if file name contains Chinese captions
  456. * @param {String} fh Code
  457. * @param {String} title File name
  458. * @returns {Boolean} Whether it contains Chinese captions
  459. */
  460. function checkChineseCaptions(fh, title) {
  461. if (title.includes("中文字幕")) {
  462. return true;
  463. }
  464. const regex = new RegExp(`${fh}-C`, 'i');
  465. return regex.test(title);
  466. }
  467.  
  468. /**
  469. * Extract code from file name
  470. * @param {String} title File name
  471. * @returns {String|null} Extracted code or null
  472. */
  473. function getVideoCode(title) {
  474. title = title.toUpperCase()
  475. .replace("SIS001", "")
  476. .replace("1080P", "")
  477. .replace("720P", "")
  478. .trim();
  479.  
  480. const patterns = [
  481. /FC2-PPV-\d+/,
  482. /1PONDO-\d{6}-\d{2,4}/,
  483. /HEYZO-?\d{4}/,
  484. /CARIB-\d{6}-\d{3}/,
  485. /N-\d{4}/,
  486. /JUKUJO-\d{4}/,
  487. /[A-Z]{2,5}-\d{3,5}/,
  488. /\d{6}-\d{2,4}/,
  489. /[A-Z]+\d{3,5}/,
  490. /[A-Za-z]+-?\d+/,
  491. /\d+-?\d+/
  492. ];
  493.  
  494. for (let pattern of patterns) {
  495. let match = title.match(pattern);
  496. if (match) {
  497. let code = match[0];
  498. console.log(`Found code: ${code}`);
  499. return code;
  500. }
  501. }
  502.  
  503. console.warn("Code not found:", title);
  504. return null; // Return null if not found
  505. }
  506.  
  507. /**
  508. * Inject progress bar into the page
  509. */
  510. function injectProgressBar() {
  511. const progressBarContainer = document.createElement('div');
  512. progressBarContainer.id = 'renameProgressBarContainer';
  513. Object.assign(progressBarContainer.style, {
  514. position: 'fixed',
  515. top: '100px',
  516. right: '10px',
  517. width: '320px',
  518. padding: '10px',
  519. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  520. color: '#fff',
  521. borderRadius: '5px',
  522. zIndex: '9999',
  523. display: 'none'
  524. });
  525.  
  526. const title = document.createElement('div');
  527. title.innerText = '115Rename Progress';
  528. Object.assign(title.style, {
  529. marginBottom: '5px',
  530. fontWeight: 'bold'
  531. });
  532.  
  533. const progress = document.createElement('div');
  534. progress.id = 'renameProgressBar';
  535. Object.assign(progress.style, {
  536. width: '100%',
  537. backgroundColor: '#555',
  538. borderRadius: '3px',
  539. overflow: 'hidden',
  540. position: 'relative'
  541. });
  542.  
  543. const progressFill = document.createElement('div');
  544. progressFill.id = 'renameProgressFill';
  545. Object.assign(progressFill.style, {
  546. width: '0%',
  547. height: '10px',
  548. backgroundColor: '#4caf50'
  549. });
  550.  
  551. const progressText = document.createElement('div');
  552. progressText.id = 'renameProgressText';
  553. Object.assign(progressText.style, {
  554. position: 'absolute',
  555. top: '0',
  556. left: '50%',
  557. transform: 'translateX(-50%)',
  558. fontSize: '12px',
  559. lineHeight: '10px',
  560. color: '#fff',
  561. pointerEvents: 'none'
  562. });
  563.  
  564. progress.appendChild(progressFill);
  565. progress.appendChild(progressText);
  566. progressBarContainer.appendChild(title);
  567. progressBarContainer.appendChild(progress);
  568.  
  569. document.body.appendChild(progressBarContainer);
  570. }
  571.  
  572. /**
  573. * Update progress bar
  574. */
  575. function updateProgressBar() {
  576. const container = $('#renameProgressBarContainer');
  577. const fill = $('#renameProgressFill');
  578. const text = $('#renameProgressText');
  579.  
  580. if (progressData.processed === 0 && requestQueue.length === 0 && activeRequests === 0) {
  581. container.hide();
  582. return;
  583. }
  584.  
  585. container.show();
  586.  
  587. const percent = ((progressData.processed / progressData.total) * 100).toFixed(2);
  588. fill.css('width', `${percent}%`);
  589. text.text(`${progressData.processed} / ${progressData.total}`);
  590.  
  591. if (progressData.processed >= progressData.total) {
  592. container.hide();
  593. enqueueNotification(`Rename completed: Success ${progressData.success}, Failed ${progressData.failed}.`, "info");
  594. console.log(`Rename completed: Success ${progressData.success}, Failed ${progressData.failed}.`);
  595. }
  596. }
  597.  
  598. /**
  599. * Update file name displayed on the page
  600. * @param {jQuery} $item jQuery object of the file
  601. * @param {String} newName New file name
  602. */
  603. function updateDOM($item, newName) {
  604. $item.attr("title", newName);
  605.  
  606. const fileNameElement = $item.find(".file_name");
  607. if (fileNameElement.length > 0) {
  608. fileNameElement.text(newName);
  609. } else {
  610. $item.text(newName);
  611. }
  612. }
  613.  
  614. // Initialize the script
  615. init();
  616.  
  617. })();

QingJ © 2025

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