Google Finance Highlights via Gemini AI

Google Finance with AI-Generated Summary via Gemini

// ==UserScript==
// @name        Google Finance Highlights via Gemini AI
// @description Google Finance with AI-Generated Summary via Gemini
// @version         0.1
// @license         MIT
// @namespace  djshigel
// @match        https://www.google.com/finance/*
// @run-at       document-end
// ==/UserScript==

(async () => {
    const GEMINI_API_KEY = 'PASTE YOUR GOOGLE GENERATIVE LANGUAGE API KEY HERE';
    const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=${GEMINI_API_KEY}`;
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // ########## Highlight ##########
    const insertHighlightElement = () => {
        const contentElements = document.querySelectorAll('body>c-wiz>div>div');
        if (contentElements.length >= 4) {
            const targetInsertPosition = contentElements[3];
            const highlightElement = document.createElement('div');
            highlightElement.id = 'gemini-highlight';
            highlightElement.innerHTML = `
                <div style='
                    font-size: 1.5em; 
                    margin-bottom: 10px; 
                    -webkit-background-clip: text!important; 
                    -webkit-text-fill-color: transparent; 
                    background: linear-gradient(to right, #4698e2, #c6657b); 
                    width: fit-content;' id='gemini-highlight-header'>
                    ✦ Highlights via Gemini
                </div>
                <div id='gemini-highlight-content'>
                </div>`;
            targetInsertPosition.prepend(highlightElement);
        }
    };

    const processHighlight = async (urls) => {
        for (let attempt = 0; attempt < 3; attempt++) {
            try {
                document.querySelector('#gemini-ticker').style.opacity = '1';
                const response = await fetch(apiUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        contents: [{
                            parts: [{
                                text: `Below are some recent news highlights, with 10 sentences to dive deeper into: 
                                ${urls}`
                            }],
                        }]
                    }),
                });

                if (!response.ok) throw new Error('Network response was not ok');

                const reader = response.body.getReader();
                let result = '', done = false, decoder = new TextDecoder();
                while (!done) {
                    const { value, done: doneReading } = await reader.read();
                    done = doneReading;
                    if (value) result += decoder.decode(value, { stream: true });
                }
                result += decoder.decode();

                const data = JSON.parse(result);
                let summary = (data.candidates[0]?.content?.parts[0]?.text || '').replace(/\*\*/g, '').replace(/##/g, '');
                console.log(`highlights: ${summary}`);

                insertHighlightElement();
                let targetElement = document.querySelector('#gemini-highlight-content');
                if (!targetElement) {
                    console.error('No target element found for summary insertion');
                    return;
                }
            
                let displayText = targetElement.textContent + ' ';
                for (const char of summary) {
                    document.querySelector('#gemini-ticker').style.opacity = '1';
                    displayText += char + '●';
                    targetElement.textContent = displayText;
                    await delay(2);
                    displayText = displayText.slice(0, -1);
                    document.querySelector('#gemini-ticker').style.opacity = '0';
                }
                targetElement.textContent = displayText;
                return;
            } catch (error) {
                document.querySelector('#gemini-ticker').style.opacity = '0';
                await delay(5000);
                console.error('Error:', error);
            }
        }
    };

    // ########## Ticker ##########
    const insertTickerElement = () => {
        if (document.querySelector('#gemini-ticker')) return;
        const ticker = document.createElement('div');
        ticker.id = 'gemini-ticker';
        ticker.style.position = 'fixed';
        ticker.style.right = '20px';
        ticker.style.bottom = '10px';
        ticker.style.fontSize = '1.5em';
        ticker.style.color = '#77777777';
        ticker.style.transition = 'opacity .3s';
        ticker.style.zIndex = '100';
        ticker.innerHTML = '✦';
        document.querySelector('body').appendChild(ticker);
    };

    // ########## Main ##########
    await delay(1000);
    insertTickerElement();
    for (let j = 0; j < 10 ; j++) {
        console.log(`######## attempt: ${j+1} ########`)
        document.querySelector('#gemini-ticker').style.opacity = '1';
        const articles = Array.from(document.querySelectorAll('article'));
        const allLinks = Array.from(document.querySelectorAll('a[href]'));
        if (allLinks.length == 0) break;
        
        if (!document.querySelector('#gemini-highlight')) {
            const urls = allLinks.map(links => {
                if (links.length == 0) return null;
                const href = links.getAttribute('href');
                const title = links.textContent;
                return `${title}: ${href}`;
            }).filter(Boolean).join(' ');
            console.log(`highlight: ${urls}`)
            await processHighlight(urls);
            await delay(1000);
        }

        document.querySelector('#gemini-ticker').style.opacity = '0';
        await delay(1000);
    }
    document.querySelector('#gemini-ticker').style.opacity = '0';
    console.log('######## Ended up all ########')
})();