Greasy Fork 还支持 简体中文。

TTV Auto Upload

Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao

目前為 2025-03-08 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name TTV Auto Upload
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Tự động điền form đăng chương trên tangthuvien.net với tính năng nâng cao
  6. // @author Your name
  7. // @match https://tangthuvien.net/dang-chuong/story/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Thêm CSS cho thông báo và nút
  15. const style = document.createElement('style');
  16. style.textContent = `
  17. .ttv-notification {
  18. position: fixed;
  19. top: 20px;
  20. right: 20px;
  21. padding: 10px 20px;
  22. background: #4CAF50;
  23. color: white;
  24. border-radius: 4px;
  25. z-index: 9999;
  26. display: none;
  27. }
  28. .ttv-error {
  29. background: #f44336;
  30. }
  31. .ttv-button-group {
  32. margin-top: 10px;
  33. display: flex;
  34. gap: 10px;
  35. align-items: center;
  36. }
  37. .ttv-file-input {
  38. display: none;
  39. }
  40. .ttv-file-label {
  41. padding: 6px 12px;
  42. background: #5bc0de;
  43. color: white;
  44. border-radius: 4px;
  45. cursor: pointer;
  46. display: inline-block;
  47. margin: 0;
  48. }
  49. .ttv-file-label:hover {
  50. background: #46b8da;
  51. }
  52. `;
  53. document.head.appendChild(style);
  54.  
  55. // Tạo div thông báo
  56. const notification = document.createElement('div');
  57. notification.className = 'ttv-notification';
  58. document.body.appendChild(notification);
  59.  
  60. // Hiển thị thông báo
  61. function showNotification(message, isError = false) {
  62. notification.textContent = message;
  63. notification.className = 'ttv-notification' + (isError ? ' ttv-error' : '');
  64. notification.style.display = 'block';
  65. setTimeout(() => {
  66. notification.style.display = 'none';
  67. }, 3000);
  68. }
  69.  
  70. // Đọc nội dung file
  71. function readFileContent(file) {
  72. return new Promise((resolve, reject) => {
  73. const reader = new FileReader();
  74. reader.onload = (e) => resolve(e.target.result);
  75. reader.onerror = (e) => reject(new Error('Lỗi đọc file: ' + e.target.error));
  76. reader.readAsText(file, 'UTF-8');
  77. });
  78. }
  79.  
  80. // Xử lý khi chọn file
  81. async function handleFileSelect(event) {
  82. try {
  83. const file = event.target.files[0];
  84. if (!file) return;
  85.  
  86. // Đọc nội dung file
  87. const content = await readFileContent(file);
  88.  
  89. // Tự động điền nội dung
  90. const contentInput = document.querySelector('textarea[name="introduce[1]"]');
  91. if (contentInput) {
  92. contentInput.value = content;
  93.  
  94. // Thử tự động lấy tên chương từ dòng đầu tiên
  95. const firstLine = content.split('\n')[0].trim();
  96. if (firstLine.includes('Chương')) {
  97. const chapterNameInput = document.querySelector('input[name="chap_name[1]"]');
  98. if (chapterNameInput) {
  99. chapterNameInput.value = firstLine;
  100. }
  101. }
  102.  
  103. showNotification('Đã điền nội dung từ file thành công!');
  104. } else {
  105. showNotification('Không tìm thấy ô nhập nội dung!', true);
  106. }
  107. } catch (error) {
  108. console.error('Lỗi xử lý file:', error);
  109. showNotification('Có lỗi xảy ra khi đọc file!', true);
  110. }
  111. }
  112.  
  113. // Thêm các nút điều khiển
  114. function addControlButtons() {
  115. const actionDiv = document.getElementById('action-btns');
  116. if (!actionDiv) {
  117. showNotification('Không tìm thấy vị trí để thêm nút!', true);
  118. return;
  119. }
  120.  
  121. const buttonGroup = document.createElement('div');
  122. buttonGroup.className = 'ttv-button-group col-sm-8 col-sm-offset-2';
  123.  
  124. // Nút tự động điền
  125. const autoFillBtn = document.createElement('button');
  126. autoFillBtn.type = 'button';
  127. autoFillBtn.className = 'btn btn-primary';
  128. autoFillBtn.innerHTML = '<span>Tự động điền</span>';
  129. autoFillBtn.onclick = autoFillForm;
  130.  
  131. // Input chọn file
  132. const fileInput = document.createElement('input');
  133. fileInput.type = 'file';
  134. fileInput.accept = '.txt';
  135. fileInput.className = 'ttv-file-input';
  136. fileInput.id = 'ttv-file-input';
  137. fileInput.onchange = handleFileSelect;
  138.  
  139. // Label cho input file
  140. const fileLabel = document.createElement('label');
  141. fileLabel.htmlFor = 'ttv-file-input';
  142. fileLabel.className = 'ttv-file-label';
  143. fileLabel.innerHTML = '<span>Chọn file txt</span>';
  144.  
  145. // Nút lưu cấu hình
  146. const saveConfigBtn = document.createElement('button');
  147. saveConfigBtn.type = 'button';
  148. saveConfigBtn.className = 'btn btn-success';
  149. saveConfigBtn.innerHTML = '<span>Lưu cấu hình</span>';
  150. saveConfigBtn.onclick = saveChapterConfig;
  151.  
  152. // Nút tải cấu hình
  153. const loadConfigBtn = document.createElement('button');
  154. loadConfigBtn.type = 'button';
  155. loadConfigBtn.className = 'btn btn-info';
  156. loadConfigBtn.innerHTML = '<span>Tải cấu hình</span>';
  157. loadConfigBtn.onclick = loadChapterConfig;
  158.  
  159. buttonGroup.appendChild(autoFillBtn);
  160. buttonGroup.appendChild(fileInput);
  161. buttonGroup.appendChild(fileLabel);
  162. buttonGroup.appendChild(saveConfigBtn);
  163. buttonGroup.appendChild(loadConfigBtn);
  164.  
  165. actionDiv.appendChild(buttonGroup);
  166. }
  167.  
  168. // Tự động điền form
  169. function autoFillForm() {
  170. try {
  171. // Kiểm tra CSRF token
  172. const tokenInput = document.querySelector('input[name="_token"]');
  173. if (!tokenInput) {
  174. showNotification('Không tìm thấy token!', true);
  175. return;
  176. }
  177.  
  178. // Lấy thông tin chương hiện tại
  179. const chap_stt = document.querySelector('.chap_stt1')?.value;
  180. const chap_serial = document.querySelector('.chap_serial')?.value;
  181. const chap_vol = document.querySelector('.chap_vol')?.value;
  182. const chap_vol_name = document.querySelector('.chap_vol_name')?.value;
  183.  
  184. if (!chap_stt || !chap_serial) {
  185. showNotification('Không tìm thấy thông tin chương!', true);
  186. return;
  187. }
  188.  
  189. // Điền các trường
  190. const fields = {
  191. 'chap_stt[1]': chap_stt,
  192. 'chap_number[1]': chap_serial,
  193. 'vol[1]': chap_vol || '1',
  194. 'vol_name[1]': chap_vol_name || '',
  195. 'chap_name[1]': `Chương ${chap_serial}`
  196. };
  197.  
  198. for (const [name, value] of Object.entries(fields)) {
  199. const input = document.querySelector(`input[name="${name}"]`);
  200. if (input) {
  201. input.value = value;
  202. }
  203. }
  204.  
  205. // Focus vào ô nội dung
  206. const contentInput = document.querySelector('textarea[name="introduce[1]"]');
  207. if (contentInput) {
  208. contentInput.focus();
  209. showNotification('Đã điền form thành công!');
  210. } else {
  211. showNotification('Không tìm thấy ô nhập nội dung!', true);
  212. }
  213. } catch (error) {
  214. console.error('Lỗi khi tự động điền form:', error);
  215. showNotification('Có lỗi xảy ra khi điền form!', true);
  216. }
  217. }
  218.  
  219. // Lưu cấu hình chương
  220. function saveChapterConfig() {
  221. try {
  222. const config = {
  223. chap_stt: document.querySelector('.chap_stt1')?.value,
  224. chap_serial: document.querySelector('.chap_serial')?.value,
  225. chap_vol: document.querySelector('.chap_vol')?.value,
  226. chap_vol_name: document.querySelector('.chap_vol_name')?.value
  227. };
  228.  
  229. localStorage.setItem('ttv_chapter_config', JSON.stringify(config));
  230. showNotification('Đã lưu cấu hình thành công!');
  231. } catch (error) {
  232. console.error('Lỗi khi lưu cấu hình:', error);
  233. showNotification('Có lỗi xảy ra khi lưu cấu hình!', true);
  234. }
  235. }
  236.  
  237. // Tải cấu hình chương
  238. function loadChapterConfig() {
  239. try {
  240. const savedConfig = localStorage.getItem('ttv_chapter_config');
  241. if (!savedConfig) {
  242. showNotification('Không tìm thấy cấu hình đã lưu!', true);
  243. return;
  244. }
  245.  
  246. const config = JSON.parse(savedConfig);
  247.  
  248. // Cập nhật các trường
  249. const fields = {
  250. '.chap_stt1': config.chap_stt,
  251. '.chap_serial': config.chap_serial,
  252. '.chap_vol': config.chap_vol,
  253. '.chap_vol_name': config.chap_vol_name
  254. };
  255.  
  256. for (const [selector, value] of Object.entries(fields)) {
  257. const input = document.querySelector(selector);
  258. if (input && value) {
  259. input.value = value;
  260. }
  261. }
  262.  
  263. showNotification('Đã tải cấu hình thành công!');
  264. } catch (error) {
  265. console.error('Lỗi khi tải cấu hình:', error);
  266. showNotification('Có lỗi xảy ra khi tải cấu hình!', true);
  267. }
  268. }
  269.  
  270. // Thêm các nút điều khiển khi trang đã load
  271. window.addEventListener('load', function() {
  272. addControlButtons();
  273. });
  274. })();