SoundCloud Media Feed Tracker

Track titles and artists of songs played on your soundcloud feed, and shows links as played, with exportSongs() feature.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         SoundCloud Media Feed Tracker
// @version      1.0.0
// @author       LucasTavaresA
// @namespace    https://gist.github.com/LucasTavaresA/51b9a4b36dd7070f96abddf7948dae94
// @license      GPL-3.0-or-later
// @description  Track titles and artists of songs played on your soundcloud feed, and shows links as played, with exportSongs() feature.
// @grant        unsafeWindow
// @match        https://soundcloud.com/feed
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    function exportSongs() {
        const tracks = localStorage.getItem(STORAGE_KEY) || JSON.stringify([], null, 2);
        const blob = new Blob([tracks], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'sc-tracks.json';

        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);

        URL.revokeObjectURL(url);
    }

    function loadHistory() {
        try {
            const saved = localStorage.getItem(STORAGE_KEY);
            if (saved) {
                trackHistory = JSON.parse(saved);
                console.log(`📚 Loaded ${trackHistory.length} tracks from history`);
            }
        } catch (e) {
            console.error('Error loading history:', e);
            trackHistory = [];
        }
    }

    function saveHistory() {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(trackHistory, null, 2));
        } catch (e) {
            console.error('Error saving history:', e);
        }
    }

    function markPlayedTracks() {
        const playedUrls = new Set(trackHistory.map(t => t.url));

        document.querySelectorAll('a[href*="/"]').forEach(link => {
            const fullUrl = link.href;
            if (playedUrls.has(fullUrl)) {
                link.classList.add('sc-played-track');
            }
        });
    }

    function getTrackInfo() {
        const titleLink = document.querySelector('.playbackSoundBadge__titleLink');
        const artistLink = document.querySelector('.playbackSoundBadge__lightLink');

        if (!titleLink) return null;

        return {
            title: titleLink.getAttribute('title') || titleLink.textContent.trim(),
            artist: artistLink ? artistLink.textContent.trim() : 'Unknown',
            url: titleLink.href,
            timestamp: new Date().toISOString()
        };
    }

    function trackChanged() {
        const info = getTrackInfo();

        if (!info) return;

        if (info.url !== lastTrackUrl) {
            lastTrackUrl = info.url;

            const existingIndex = trackHistory.findIndex(t => t.url === info.url);

            if (existingIndex === -1) {
                trackHistory.push(info);
                saveHistory();
                setTimeout(markPlayedTracks, 200);
            }
        }
    }

    function setupTracker() {
        const playbackBar = document.querySelector('.playControls__soundBadge');

        if (!playbackBar) {
            setTimeout(setupTracker, 1000);
            return;
        }

        loadHistory();
        markPlayedTracks();

        let markTimeout;
        const observer = new MutationObserver(() => {
            trackChanged();
            clearTimeout(markTimeout);
            markTimeout = setTimeout(markPlayedTracks, 100);
        });

        observer.observe(playbackBar, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href', 'title']
        });

        let feedMarkTimeout;
        const feedObserver = new MutationObserver(() => {
            clearTimeout(feedMarkTimeout);
            feedMarkTimeout = setTimeout(markPlayedTracks, 100);
        });

        const feed = document.querySelector('.lazyLoadingList__list') || document.body;

        feedObserver.observe(feed, {
            childList: true,
            subtree: true
        });
    }

    const STORAGE_KEY = 'soundcloud_track_history';
    let lastTrackUrl = null;
    let trackHistory = [];

    if (window.location.href === "https://soundcloud.com/feed") {
        const style = document.createElement('style');
        style.textContent = `
        a:visited {
            color: grey !important;
            text-decoration: underline !important;
        }
        a.sc-played-track {
            color: grey !important;
            text-decoration: underline !important;
        }
    `;
        document.head.appendChild(style);

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', setupTracker);
        } else {
            setupTracker();
        }

        unsafeWindow.exportSongs = exportSongs;
    }
})();