Gracenotes ID

I made a Tampermonkey script that can scrape the tvtv.us guide for Channel Call Sign and Gracenote ID, saving it to a json file. If anyone wants it, I'll paste it here in a little while.

3 Likes

Download Tampermonkey from the Chrome Web Store

1: Enable User Scripts in the Extension Settings

  1. Right-click the Tampermonkey icon located on your browser's toolbar.
  2. Select Manage extensions from the drop-down menu. This will open a new browser settings tab specifically for the Tampermonkey extension.
  3. Scroll through the options and find the toggle labeled Allow User Scripts (recent browser security updates require this permission to be explicitly granted so Tampermonkey can inject your custom code). Click the toggle to turn it on.
  4. You can now close the extension settings tab.

2: Add the Custom Script

  1. Left-click the Tampermonkey icon on your browser's toolbar.
  2. Select Create a new script... from the menu.
  3. A text editor will open containing some default template code. Delete everything in this editor so it is completely blank.
  4. Paste the entire JavaScript code into the editor.
  5. Click File in the top menu of the Tampermonkey editor and select Save (or simply press Ctrl + S on your keyboard).

3: Run the Scraper

  1. Navigate to the tvtv.us lineup page of your choice.
  2. You will now see the blue Scrape Stations button floating in the top right corner of the page.
  3. Browse the site, select your local provider, and click the button whenever you are ready to let the script scroll and harvest the Gracenote IDs.
// ==UserScript==
// @name         tvtv.us Gracenote Scraper (Button Trigger)
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds a floating button to scrape Gracenote IDs on demand.
// @match        *://*.tvtv.us/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // 1. Inject the floating button into the page
    const btnContainer = document.createElement('div');
    btnContainer.innerHTML = '<button id="tm-scrape-btn">Scrape Stations</button>';
    document.body.appendChild(btnContainer);

    // 2. Style the button so it sits in the top right corner
    GM_addStyle(`
        #tm-scrape-btn {
            position: fixed;
            top: 80px; /* Moved to the top, offset by 80px to clear the site header */
            right: 20px;
            z-index: 99999;
            padding: 12px 20px;
            background-color: #2196F3;
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            box-shadow: 0px 4px 6px rgba(0,0,0,0.3);
        }
        #tm-scrape-btn:hover {
            background-color: #1976D2;
        }
    `);

    // 3. Attach the scraping logic to the button click
    document.getElementById("tm-scrape-btn").addEventListener("click", function() {
        this.innerText = "Scraping... (Do not touch)";
        this.style.backgroundColor = "#FF9800";
        scrapeLazyLoadedGrid(this);
    });

    async function scrapeLazyLoadedGrid(btnElement) {
        const stations = new Map();
        let retries = 0;
        const maxRetries = 3; 

        window.scrollTo(0, 0);

        while (true) {
            const links = Array.from(document.querySelectorAll('a[href*="/stn"]'));
            
            links.forEach(a => {
                const match = a.href.match(/\/stn(\d+)(?:-[0-9.]+)?(?:-([a-zA-Z0-9_-]+))?/);
                if (match) {
                    const gracenoteId = match[1];
                    const callSign = match[2] || "UNKNOWN";
                    if (!stations.has(gracenoteId)) {
                        stations.set(gracenoteId, {
                            callSign: callSign,
                            gracenoteId: gracenoteId
                        });
                    }
                }
            });

            window.scrollBy(0, window.innerHeight);
            await new Promise(resolve => setTimeout(resolve, 800));

            let currentScroll = Math.ceil(document.documentElement.scrollTop + window.innerHeight);
            let scrollHeight = document.documentElement.scrollHeight;

            if (currentScroll >= scrollHeight) {
                retries++;
                if (retries >= maxRetries) {
                    break;
                }
                await new Promise(resolve => setTimeout(resolve, 1000));
            } else {
                retries = 0; 
            }
        }

        const stationList = Array.from(stations.values());
        const jsonString = JSON.stringify(stationList, null, 2);

        let fileName = "gracenote_stations.json";
        const urlMatch = window.location.pathname.match(/\/lu([^/]+)/);
        if (urlMatch && urlMatch[1]) {
            fileName = `${urlMatch[1]}_stations.json`;
        }

        const blob = new Blob([jsonString], { type: "application/json" });
        const url = URL.createObjectURL(blob);

        const downloadLink = document.createElement('a');
        downloadLink.href = url;
        downloadLink.download = fileName;
        document.body.appendChild(downloadLink);
        downloadLink.click();

        document.body.removeChild(downloadLink);
        URL.revokeObjectURL(url);

        // Reset button
        btnElement.innerText = "Scrape Stations";
        btnElement.style.backgroundColor = "#2196F3";
        alert(`Scraping complete! Downloaded ${stationList.length} stations.`);
    }
})();

Very cool!

Maybe this could be used to create a shared list with all channels. People could run this script and add missing channels to the list.

Nice use of Tampermonkey! Anyone who's using Project WebUI+ will already have the extension installed. Chrome or Firefox.

Cool tool! Thanks, @Bobby_Vaughn!

Don't we already have a decent list here?

I'm not in the US, so don't know how complete it is for you lot.

This is awesome and I totally stole this for the project I am working on. Here is a tip though if you add /faves to the url for example https://www.tvtv.us/us/lineups/luUSA-YTBE544-X/faves and scrape that, it will give you Callsign, Channel Name, Gracenote ID, and logo url :slight_smile: