自動下單零股

將需要購買的零股代號一次輸入到下方多行區塊後(建議整理好代號後一次貼上去),將會自動為您下單到暫存。

// ==UserScript==
// @name         自動下單零股
// @namespace    https://github.com/zxc88645/TdccAuto/blob/main/SinotradeStockHelper.js
// @version      1.0.5
// @description  將需要購買的零股代號一次輸入到下方多行區塊後(建議整理好代號後一次貼上去),將會自動為您下單到暫存。
// @author       Owen
// @match        https://www.sinotrade.com.tw/inside/Batch_Order
// @icon         https://raw.githubusercontent.com/zxc88645/TdccAuto/refs/heads/main/img/TdccAuto_icon.png
// @grant        none
// @license      MIT
// @homepage     https://github.com/zxc88645/TdccAuto
// ==/UserScript==

(function () {
    'use strict';

    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    const selectors = {
        input: "#app-container input",
        button: "#app-container button.midbtn.submit",
        selectionMenu: "#ui-id-2",
        select: "#app-container .stockItemContainer select",
        priceButton: "#app-container .stockItemContainer button.priceBtn.smallBtn.high"
    };

    const bodyWrapper = document.querySelector(".body-wrapper");
    if (!bodyWrapper) return;

    const textArea = document.createElement("textarea");
    Object.assign(textArea.style, { width: "200px", height: "500px", display: "block", marginLeft: "50px" });
    textArea.placeholder = "輸入數字,每行一筆...";
    bodyWrapper.appendChild(textArea);

    let isProcessing = false;

    async function processNext() {
        if (isProcessing) return;
        isProcessing = true;

        const inputElement = document.querySelector(selectors.input);
        const buttonElement = document.querySelector(selectors.button);
        const selectionMenu = document.querySelector(selectors.selectionMenu);
        const selectElement = document.querySelector(selectors.select);
        const priceButton = document.querySelector(selectors.priceButton);

        if (!inputElement || !buttonElement || !selectionMenu || !selectElement || !priceButton) {
            console.warn("必要的 DOM 元素未找到,流程終止");
            isProcessing = false;
            return;
        }

        let lines = textArea.value.split("\n").map(line => line.trim()).filter(line => line);
        if (lines.length === 0) {
            isProcessing = false;
            return;
        }

        let value = lines.shift();
        textArea.value = lines.join("\n");

        await simulateTyping(inputElement, value);
        await sleep(1000);

        if (selectionMenu.childNodes.length > 0 && (selectionMenu.childNodes[0].innerText).startsWith(value + ' ')) {
            // 顯示該元素文字
            console.log(`[選擇] ${selectionMenu.childNodes[0].innerText}`);

            selectionMenu.childNodes[0].click();
            await sleep(500);
        } else {
            console.warn("所查詢股票不存在或不正確,流程中斷");
            isProcessing = false;
            return;
        }

        if (!(await selectOptionByValue(selectElement, "C"))) {
            console.warn("選項 C 不存在或不可見,流程中斷");
            isProcessing = false;
            return;
        }

        await sleep(500);
        await simulateClick(priceButton); // 點擊"漲"按鈕
        await sleep(300);
        await simulateClick(buttonElement); // 點擊"暫存"按鈕
        await sleep(100);
        isProcessing = false;
    }

    async function simulateTyping(element, text) {
        if (!element) return;
        element.focus();
        element.value = "";

        for (let char of text) {
            element.dispatchEvent(new KeyboardEvent("keydown", { key: char, bubbles: true }));
            element.value += char;
            element.dispatchEvent(new InputEvent("input", { bubbles: true }));
            element.dispatchEvent(new KeyboardEvent("keyup", { key: char, bubbles: true }));
            await sleep(50);
        }
    }

    async function selectOptionByValue(selectElement, value) {
        if (!selectElement) return false;

        let option = Array.from(selectElement.options).find(opt => opt.value === value && opt.style.display !== "none");
        if (option) {
            selectElement.value = value;
            selectElement.dispatchEvent(new Event("change", { bubbles: true }));
            console.log(`成功選擇值為 "${value}" 的選項`);
            return true;
        }
        return false;
    }

    async function simulateClick(element) {
        if (!element) return;
        element.click();
        await sleep(100);
    }

    setInterval(() => {
        if (!isProcessing && textArea.value.trim()) {
            processNext();
        }
    }, 1000);
})();