Greasy Fork is available in English.

電子投票自動投票

自動電子投票,並且快速將結果保存成 JPG

Stan na 31-03-2025. Zobacz najnowsza wersja.

  1. // ==UserScript==
  2. // @name 電子投票自動投票
  3. // @namespace https://github.com/zxc88645/TdccAuto
  4. // @version 1.5.2
  5. // @description 自動電子投票,並且快速將結果保存成 JPG
  6. // @author Owen
  7. // @match https://stockservices.tdcc.com.tw/*
  8. // @icon https://raw.githubusercontent.com/zxc88645/TdccAuto/refs/heads/main/img/TdccAuto_icon.png
  9. // @grant none
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
  12. // @license MIT
  13. // @homepage https://github.com/zxc88645/TdccAuto
  14. // ==/UserScript==
  15.  
  16. /* global html2pdf */
  17.  
  18. (function () {
  19. 'use strict';
  20.  
  21. /** 延遲函式 */
  22. const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  23.  
  24. /**
  25. * 查詢 DOM 元素,支援 CSS 選擇器與 XPath
  26. * @param {string} selector - CSS 選擇器或 XPath
  27. * @param {Element} [context=document] - 查詢範圍
  28. * @returns {Element|null} - 匹配的 DOM 元素
  29. */
  30. function querySelector(selector, context = document) {
  31. return selector.startsWith('/') || selector.startsWith('(') ? document.evaluate(selector, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
  32. : context.querySelector(selector);
  33. }
  34.  
  35. /**
  36. * 點擊指定元素並等待執行完成
  37. * @param {string} selector - CSS 選擇器或 XPath
  38. * @param {string} [expectedText=null] - 預期的文字內容
  39. * @param {string} [logInfo=null] - 日誌輸出標籤
  40. */
  41. async function clickAndWait(selector, expectedText = null, logInfo = null) {
  42. try {
  43. const element = querySelector(selector);
  44. if (!element) {
  45. console.warn(`[未找到] ${selector}`);
  46. return;
  47. }
  48.  
  49. if (expectedText && element.innerText.trim() !== expectedText) {
  50. console.warn(`[文字不匹配] 預期: '${expectedText}',但實際為: '${element.innerText.trim()}'`);
  51. return;
  52. }
  53.  
  54. console.log(`[點擊] ${selector} ${logInfo ? `| ${logInfo}` : ''}`);
  55. element.click();
  56. await sleep(100);
  57. } catch (error) {
  58. console.error(`[錯誤] 點擊失敗: ${selector}`, error);
  59. }
  60. }
  61.  
  62. /**
  63. * 下載 PDF
  64. */
  65. function savePDF() {
  66. const element = document.querySelector("body > div.c-main > form");
  67. if (!element) return;
  68.  
  69. const children = Array.from(element.children).slice(0, 4);
  70. const tempDiv = document.createElement("div");
  71. children.forEach(el => tempDiv.appendChild(el.cloneNode(true)));
  72.  
  73. // 提取股票代號
  74. const text = document.querySelector("body > div.c-main > form > div.c-votelist_title > h2")?.innerText.trim();
  75. const match = text?.match(/貴股東對(\d+)\s/);
  76. const stockNumber = match ? match[1] : "投票結果";
  77.  
  78. html2pdf()
  79. .set({
  80. margin: 1,
  81. filename: `${stockNumber}.pdf`,
  82. image: { type: 'jpeg', quality: 1 },
  83. html2canvas: { scale: 2, useCORS: true },
  84. jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
  85. })
  86. .from(tempDiv)
  87. .save();
  88. }
  89.  
  90.  
  91. /**
  92. * 下載 JPG
  93. */
  94. function saveAsJPG() {
  95. const element = document.querySelector("body > div.c-main > form");
  96. if (!element) return;
  97.  
  98. const children = Array.from(element.children).slice(0, 4);
  99. const tempDiv = document.createElement("div");
  100. tempDiv.style.background = "white"; // 確保背景是白的
  101. children.forEach(el => tempDiv.appendChild(el.cloneNode(true)));
  102.  
  103. // 把 tempDiv 暫時加到 body 中,讓 html2canvas 能正確渲染
  104. document.body.appendChild(tempDiv);
  105. tempDiv.style.position = 'absolute';
  106. tempDiv.style.left = '-9999px';
  107.  
  108. // 提取股票代號
  109. const text = document.querySelector("body > div.c-main > form > div.c-votelist_title > h2")?.innerText.trim();
  110. const match = text?.match(/貴股東對(\d+)\s/);
  111. const stockNumber = match ? match[1] : "投票結果";
  112.  
  113. html2canvas(tempDiv, { scale: 2, useCORS: true }).then(canvas => {
  114. const link = document.createElement("a");
  115. link.href = canvas.toDataURL("image/jpeg", 1.0);
  116. link.download = `${stockNumber}.jpg`;
  117. link.click();
  118. document.body.removeChild(tempDiv); // 清除暫時元素
  119. });
  120. }
  121.  
  122.  
  123. /**
  124. * 主程式
  125. */
  126. async function main() {
  127. const currentPath = window.location.pathname;
  128. console.log(`[當前網址] ${currentPath}`);
  129.  
  130. if (currentPath.includes('/evote/shareholder/001/6_01.html')) {
  131. console.log('進行電子投票 - 最後的確認');
  132.  
  133. await clickAndWait('#go', '確認', '確認');
  134. } else if (currentPath.includes('/evote/shareholder/001/')) {
  135. console.log('進行電子投票 - 投票中');
  136.  
  137. // 全部棄權
  138. await clickAndWait('body > div.c-main > form > table:nth-child(3) > tbody > tr.u-t_align--right > td:nth-child(2) > a:nth-child(3)', '全部棄權', '勾選全部棄權(1)');
  139. await clickAndWait('body > div.c-main > form > div.c-votelist_actions > button:nth-child(2)', '下一步', '按下 下一步(1)');
  140.  
  141. // 全部棄權 2
  142. await clickAndWait('#voteform > table:nth-child(5) > tbody > tr > td.u-t_align--right > a:nth-child(8)', '全部棄權', '勾選全部棄權(2)');
  143. await clickAndWait('#voteform > div.c-votelist_actions > button:nth-child(1)', '下一步', '按下 下一步(2)');
  144. await clickAndWait('body > div.jquery-modal.blocker.current > div > div:nth-child(2) > button:nth-child(1)', '下一步', '按下 下一步(2.2)');
  145.  
  146. // 確認投票結果
  147. await clickAndWait('body > div.c-main > form > div.c-votelist_actions > button:nth-child(1)', '確認投票結果', '確認投票結果');
  148.  
  149. // 最後的確認
  150.  
  151.  
  152. } else if (currentPath === '/evote/shareholder/000/tc_estock_welshas.html') {
  153. console.log('位於投票列表首頁');
  154. await clickAndWait('//*[@id="stockInfo"]/tbody/tr[1]/td[4]/a[1]', '投票', '進入投票');
  155.  
  156. } else if (currentPath === '/evote/shareholder/002/01.html') {
  157. console.log('準備列印投票結果');
  158. if (document.querySelector("#printPage")?.innerText.trim() === '列印') {
  159. saveAsJPG();
  160. }
  161.  
  162. } else {
  163. console.warn('當前網址不在預期範圍內');
  164. }
  165.  
  166. console.log('✅ 完成');
  167. }
  168.  
  169. // 在網頁完全載入後執行 main 函式
  170. window.addEventListener("load", () => setTimeout(main, 500));
  171. })();