Neopets: Stocks enhancer

Show only the stocks you want, and adds columns for monthly highs and lows

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Neopets: Stocks enhancer
// @author       Tombaugh Regio
// @version      1.4
// @description  Show only the stocks you want, and adds columns for monthly highs and lows
// @namespace    https://greasyfork.org/users/780470
// @match        http://www.neopets.com/stockmarket.phtml?type=list&search=%&bargain=true
// @match        http://www.neopets.com/stockmarket.phtml?type=portfolio
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// ==/UserScript==

// ======================================================================

const PRICE = {
    //Lowest price that can be purchased. Default lowest is 15
    lowest: 15,

    //Highest price to consider
    highest: 15
}

// ======================================================================

//Get stock market data from Neostocks
GM_xmlhttpRequest({
    method: 'GET',
    url: 'https://neostocks.info/?period=1m',
    headers: {
        'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
        'Accept': 'application/atom+xml,application/xml,text/xml',
    },
    onload: function(responseDetails) {
        return new Promise((resolve, reject) => {
            resolve(GM_setValue("responseText", responseDetails.responseText))
        })
    }
})

const stockData = GM_getValue("responseText").match(/{"summary_data":{.+"1m":\[{(.+)}\],"all":.+}/)[1]
const stockTickerInfos = stockData.split("},{")
const monthlyStocks = []

for(const datapoints of stockTickerInfos) {
    const datapoint = datapoints.split(",")
    const ticker = datapoint[0].match(/.+:"(.+)"/)[1]
    const high = datapoint[4].match(/\d+/)[0]
    const low = datapoint[6].match(/\d+/)[0]

    monthlyStocks.push({ticker: ticker, high: high, low: low})
}

let stocksTable, stockContainer
const URL = document.URL

if(URL.includes("portfolio")) {

    //Get the table with all the stock information
    stocksTable = document.evaluate("//a[contains(., 'here')]",
                                    document, null, XPathResult.ANY_TYPE, null ).iterateNext()
        .parentNode.querySelector("table")
    stocksTable.style.marginTop = "2em"
    stockContainer = stocksTable.querySelectorAll("tr")

    //Separate stock headers from rows, and push headers into an array
    let portfolioHeaders, portfolioSubheaders, portfolioRows
    [portfolioHeaders, portfolioSubheaders, ...portfolioRows] = stockContainer

    let topHeaderTitles = [], subheaderTitles = []

    const setCustomHeader = (i, number, header, HTML) => {if(i == number) header.innerHTML = HTML}
    function setNewHeaders(headers, titles, ARRAY){
        for (const [i, header] of headers.childNodes.entries()) {
            for(const OBJECT of ARRAY) {
                setCustomHeader(i, OBJECT.number, header, OBJECT.HTML)
            }
            if (header.nodeName != "#text") {titles.push(header.textContent)}
        }
    }

    setNewHeaders(portfolioHeaders, topHeaderTitles, [{number: 3, HTML: `<b>Month</b>`}])
    setNewHeaders(portfolioSubheaders, subheaderTitles, [{number: 5, HTML: `<b>High</b>`},{number: 9, HTML: `<b>Low</b>`}])


    for (const row of portfolioRows) {
        const cells = row.querySelectorAll("td")
        for (const [i, cell] of cells.entries()) {
            if (cells.length == 9){

                //Find the columns with highs and lows, and set their contents to data from Neostocks
                if (i == subheaderTitles.indexOf("Ticker")) {
                    const cellTicker = cell.querySelector("a").textContent
                    for (const stock of monthlyStocks) {
                        if (cellTicker == stock.ticker) {
                            const cellHigh = cells[subheaderTitles.indexOf("High")]
                            const cellLow = cells[subheaderTitles.indexOf("Low")]
                            cellHigh.innerHTML = `<a href="https://neostocks.info/tickers/${
                                                    stock.ticker}?period=1m" ><b>${
                                                    stock.high}</b></a>`

                            cellLow.innerHTML = `<a href="https://neostocks.info/tickers/${
                                                    stock.ticker}?period=1m" ><b>${
                                                    stock.low}</b></a>`

                            cellHigh.classList.add("monthly-high")
                            cellLow.classList.add("monthly-low")
                        }
                    }
                }

                //Style the current price if it's above the monthly high or below the monthly low
                if (i == subheaderTitles.indexOf("Current Price")) {
                    const cellHigh = cells[subheaderTitles.indexOf("High")]
                    const cellLow = cells[subheaderTitles.indexOf("Low")]

                    if (parseInt(cell.textContent) > parseInt(cellHigh.textContent)) {
                        cell.innerHTML = `<font color="green"><b>${cell.textContent}</b></font>`
                    }

                    if (parseInt(cell.textContent) < parseInt(cellLow.textContent)) {
                        cell.innerHTML = `<font color="red"><b>${cell.textContent}</b></font>`
                    }
                }

                //Hide stock logos
                if (i == subheaderTitles.indexOf("Icon")) {
                    const images = cell.querySelectorAll("img")
                    for(const image of images) {
                        if (image.title.length > 0) {
                            image.style.display = "none"
                        }
                    }
                }
            }
        }
    }

    //Swap rows
    for (const [i, row] of stockContainer.entries()) {
        if (i != 0 && row.children[3] != undefined) {row.children[3].after(row.children[2])}
    }
}

if(URL.includes("bargain")) {

    //Get the table with all the stock information
    stocksTable = document.evaluate("//b[contains(., 'Bargain Stocks')]",
                                    document, null, XPathResult.ANY_TYPE, null ).iterateNext()
        .nextSibling.nextSibling.nextSibling.nextSibling

    stocksTable.style.marginTop = "2em"
    stockContainer = stocksTable.querySelectorAll("tr")

    //Add columns for monthly lows and highs
    stockContainer.forEach(row => {
        row.appendChild(row.lastChild.cloneNode(true))
        row.appendChild(row.lastChild.cloneNode(true))
    })

    //Separate stock headers from rows, and push headers into an array
    let stockHeader, stockRows
    [stockHeader, ...stockRows] = stockContainer

    let headers = []
    stockHeader.childNodes.forEach((header, i) => {
        if (i == 7) {header.innerHTML = `<font color = "white"><b>High</b></font>`}
        if(i == 8) {header.innerHTML = `<font color = "white"><b>Low</b></font>`}
        headers.push(header.innerText)
    })

    //Hide logo and change columns
    for (const row of stockContainer) {
        for (const [i, column] of row.childNodes.entries()) {
            const logoColumn = headers.findIndex(e => e == "Logo")
            const changeColumn = headers.findIndex(e => e == "Change")

            if (i == logoColumn || i == changeColumn) {column.style.display = "none"}
        }
    }

    //For each row, add monthly highs and lows, and hide them if they're outside the specified price range
    for(const row of stockRows) {
        let monthlyHighHTML = `<a href="https://neostocks.info/bargain?period=1m"
                                  title="Click to check if stocks have updated.">n/a</a>`
        let monthlyLowHTML = monthlyHighHTML

        for (const [i, cell] of row.childNodes.entries()) {
            const cellText = cell.innerText
            const tickerColumn = headers.findIndex(e => e == "Ticker")
            const priceColumn = headers.findIndex(e => e == "Curr")
            const highColumn = headers.findIndex(e => e == "High")
            const lowColumn = headers.findIndex(e => e == "Low")

            //Add monthly highs and lows
            for (const stock of monthlyStocks) {
                if (i == tickerColumn && cellText == stock.ticker) {
                    monthlyHighHTML = `<a href="https://neostocks.info/tickers/${
                    stock.ticker}?period=1m" ><b>${
                    stock.high}</b></a>`
                    monthlyLowHTML = `<a href="https://neostocks.info/tickers/${
                    stock.ticker}?period=1m" ><b>${
                    stock.low}</b></a>`
                }
            }

            if (i == highColumn) {cell.innerHTML = monthlyHighHTML}
            if (i == lowColumn) {cell.innerHTML = monthlyLowHTML}


            //Style the price column like the hidden change column
            if (i == priceColumn) {
                const fontColor = cell.nextSibling.childNodes[0].color
                const tooltipText = cell.nextSibling.textContent
                cell.innerHTML = `<a href="#" title="${
                tooltipText}"><font color="${
                fontColor}"><b>${cellText}</b></font></a>`
            }

            //If price is outside prescribed range, hide the row
            if (i == priceColumn && (cellText < PRICE.lowest || cellText > PRICE.highest)) {
                row.style.display = "none"
            }
        }
    }
}