Skip to content

Instantly share code, notes, and snippets.

@muhdiboy
Last active May 2, 2024 11:59
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muhdiboy/a293cbff355af750e3b8f45ec816d1f1 to your computer and use it in GitHub Desktop.
Save muhdiboy/a293cbff355af750e3b8f45ec816d1f1 to your computer and use it in GitHub Desktop.
LastFM automated duplicate scrobble deletion script

Why would I need this?

Your scrobbler might have decided to scrobble every song hundreds of times, and you can't really remove those scrobbles efficiently. Or you might have accidentally installed multiple scrobbler extensions at the same time - wondering why multiple scrobbles appear for every song played at a time - and you want to clear them after finding the issue.

Using this script still doesn't necessarily make the process quick since Last.fm only displays a limited number of scrobbles that can be removed on each page of your library. However unlike the implementation of @sk22 and its forks, this UserScript, which is derived from those scripts, is run once. The rest of the process is automated and the script will stop at the page you have set using the prompt.

Installation

Prerequisites

You will need some form of UserScript interpreter/injector plugin and a compatible browser.

  • For Google Chrome and Chromium-based browsers (e.g. Vivaldi, Opera or Brave), you need the Tampermonkey plugin. Just click on the link or search for it yourself on the Chrome Web Store and add the extension.
  • Currently not supported: For Firefox, you also need the Tampermonkey plugin (or alternatively Greasemonkey). Just click on one of the desired links or search for it yourself on the Firefox Browser Add-Ons page.

How to install the UserScript

Just click on the "Raw" button in the upper right-hand corner of the script file or by clicking here. Your extension should open an installation window where you confirm the installation of the UserScript. If that is not working, try to add the script manually to your extension by copying and pasting it.

Using the script - step by step

  • Open your Last.fm account's library while being logged in (https://www.last.fm/user/_/library). Navigate to the page from which you want to start removing duplicates by clicking through the pages at the bottom of the page or by entering the page number in the URL (e.g. https://www.last.fm/user/_/library?page=123).
    • Alternatively, you can choose a specific date range instead and navigate to the last or desired page.
  • Manually reload the page if the button doesn't appear.
  • You have the option to change the behaviour of the comparison process. By unchecking the checkbox, you can make the script ignore timestamps and compare over n titles. You'll be prompted to enter a number after clicking the button.
  • Click the button and enter the page number at which you want the script to stop.
    • If you have unchecked the checkbox, you will be prompted to enter a number for how many tracks you want to compare.
  • Now enjoy the purge :)
    • You can continue with other tasks on your PC. Just leave the window active in the background. I advise you to run the script in a separate window while doing other work. Using the same browser in another window is also fine. Avoid activating another tab, though.
    • It takes approximately 5 minutes to go through 100 pages (in my own case).
    • If you want to stop the script, you can click the Cancel button on the left-hand side.
  • When it reaches the page you have set at the prompt, the script will automatically stop.

Updating the script

You can set up the script to auto-update in the settings of the UserScript. Open up the plugin settings and select the "LastFM automated duplicate scrobble deletion script." Then, go to the script settings in the upper left-hand corner, check the "Search for updates" checkbox and save.

Alternatively, you can also update it manually by following the installation instructions above or searching for updates in the plugin overview.

Possible future updates

Done

  • Adding a button on the last.fm history page to initiate the script instead of activating and deactivating the Userscript.
    • added in v1.3
  • Implementing a "stop at page x" dialogue window.
    • added in v1.3
  • Implementing a "check x tracks for duplicates" dialogue window instead of editing "num" in the script
    • added in v1.3
  • Eliminate the need to manually edit the script by incorporating a checkbox or another method for configuring options.
    • added in v1.4

Thank-yous

Based on https://gist.github.com/sk22/39cc280840f9d82df574c15d6eda6629#gistcomment-3046698 Thanks to previous contributors @CennoxX, @mattsson, @gms8994, @huw and @sk22 Thanks to new contributions from @Eiron

Feel free to post any problems, fixes, improvements and feedback. I will try to help in any form. To anyone having more experience in Javascript or OpenUserJS/UserScript: your help is also needed to improve the script by implementing the above-mentioned possible future updates as I do not have the necessary skills to contribute.

// ==UserScript==
// @name LastFM automated duplicate scrobble deletion script
// @namespace https://gist.github.com/muhdiboy
// @version 1.4.5
// @downloadURL https://gist.github.com/muhdiboy/a293cbff355af750e3b8f45ec816d1f1/raw/lastfm-automated-remove-duplicates.user.js
// @updateURL https://gist.github.com/muhdiboy/a293cbff355af750e3b8f45ec816d1f1/raw/lastfm-automated-remove-duplicates.user.js
// @description Based on https://gist.github.com/sk22/39cc280840f9d82df574c15d6eda6629#gistcomment-3046698, thanks to previous contributors (@CennoxX, @mattsson, @gms8994, @huw and @sk22) and new contributors (@Eiron).
// @author muhdiboy
// @match https://www.last.fm/*user/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==
(function() {
// Variables
var runScript = localStorage.getItem('runScript') || 'false';
var rmOnlyRealDup = localStorage.getItem('rmOnlyRealDup') !== 'false'; // Set checkbox to checked/true as default
var num = localStorage.getItem('num') || "5";
var stopAtPage = localStorage.getItem('stopAtPage') || "0";
var currentPage = extractQueryParam('page', window.location.href) || 1;
window.addEventListener('click', function () {
currentPage = extractQueryParam('page', window.location.href) || 1;
});
// Functions
var pageURLCheckTimer = setInterval (
function () {
// Check the URL at regular intervals to add or remove buttons
if ( this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search) {
this.lastPathStr = location.pathname;
this.lastQueryStr = location.search;
if (shouldMatchPage()) createButtons(); else removeButtons();
}
}
, 125
);
function replaceQueryParam(param, newval, search) {
// Function to replace a query parameter in the URL
var regex = new RegExp("([?;&])" + param + "[^&;]*[;&]?");
var query = search.replace(regex, "$1").replace(/&$/, '');
return (query.length > 2 ? query + "&" : "?") + (newval ? param + "=" + newval : '');
}
function extractQueryParam( name, url ) {
// Function to extract a query parameter from the URL
if (!url) url = window.location.href;
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( url );
return results == null ? null : results[1];
}
function shouldMatchPage() {
// Function to determine if the current page should be matched
var url = window.location.href;
var excludeUrlRegex = /^https:\/\/www\.last\.fm\/.*user\/.*\/library\/(albums|artists|tracks|music).*/;
var matchUrlRegex = /^https:\/\/www\.last\.fm\/.*user\/.*\/library.*/;
return matchUrlRegex.test(url) && !excludeUrlRegex.test(url);
}
function removeButtons() {
// Function to remove all buttons from the page
var cancelButton = document.getElementById('cancelButton');
var buttonContainer = document.getElementById('buttonContainer');
if (cancelButton) cancelButton.remove();
if (buttonContainer) buttonContainer.remove();
}
// Buttons
function createButtons() {
var cancelButton = document.createElement('button');
cancelButton.id = 'cancelButton';
cancelButton.innerHTML = 'Cancel<br>Script';
cancelButton.style.position = 'fixed';
cancelButton.style.bottom = '50%';
cancelButton.style.left = '0';
cancelButton.style.zIndex = '9999';
cancelButton.style.fontFamily = 'monospace';
cancelButton.style.padding = '3px';
cancelButton.style.border = 'dashed';
if (runScript === 'true') {
cancelButton.style.color = 'white';
cancelButton.style.background = 'red';
cancelButton.style.borderColor = 'firebrick';
cancelButton.addEventListener('click', function() {
localStorage.setItem('runScript', 'false');
location.reload();
});
} else {
cancelButton.style.color = 'gray';
cancelButton.style.background = 'lightgray';
cancelButton.style.borderColor = 'lightgray';
cancelButton.style.cursor = 'default';
}
document.body.appendChild(cancelButton);
// Create the main button to trigger the script
var runButton = document.createElement('button');
runButton.textContent = 'Remove Duplicates';
runButton.style.position = 'relative';
runButton.style.display = 'block';
runButton.style.marginBottom = '5px';
runButton.style.fontFamily = 'sans-serif';
runButton.style.fontSize = 'initial';
runButton.style.border = 'outset';
runButton.style.borderColor = 'crimson';
runButton.style.color = 'white';
runButton.style.background = 'firebrick';
runButton.style.padding = '5px';
runButton.addEventListener('click', function () {
var stopLoop = false;
while (!stopLoop) {
var inputStopAtPage = prompt("Stop at which page?", stopAtPage);
if (inputStopAtPage === null) return;
if (!/^\d+$/.test(inputStopAtPage)) { alert("Invalid input. Please enter a valid number."); continue; }
if (inputStopAtPage > currentPage && !(currentPage === 0 && inputStopAtPage === 1)) { alert("Please enter a page less than or equal to your current page: " + currentPage); continue; }
localStorage.setItem('stopAtPage', inputStopAtPage);
stopLoop = true;
}
if (!rmOnlyRealDup) {
stopLoop = false;
while (!stopLoop) {
var inputNum = prompt("Enter the number of tracks to check for duplicates\n(between 2 and 50):", num);
if (inputNum === null) return;
if (!/^\d+$/.test(inputNum) || inputNum < 2 || inputNum > 50) { alert("Invalid input. Please enter a valid number between 2 and 50."); continue; }
localStorage.setItem('num', inputNum);
stopLoop = true;
}
}
localStorage.setItem('runScript', 'true');
location.reload();
});
// Create the checkbox element
var rmOnlyRealDupCheckbox = document.createElement('input');
rmOnlyRealDupCheckbox.textContent = 'Only compare real duplicates?';
rmOnlyRealDupCheckbox.type = 'checkbox';
rmOnlyRealDupCheckbox.id = 'rmOnlyRealDupCheckbox';
rmOnlyRealDupCheckbox.style.position = 'relative';
rmOnlyRealDupCheckbox.style.marginLeft = '10px';
rmOnlyRealDupCheckbox.checked = rmOnlyRealDup;
rmOnlyRealDupCheckbox.addEventListener('change', function () {
localStorage.setItem('rmOnlyRealDup', rmOnlyRealDupCheckbox.checked);
});
// Create the label for the checkbox
var rmOnlyRealDupCheckboxLabel = document.createElement('label');
rmOnlyRealDupCheckboxLabel.style.position = 'relative';
rmOnlyRealDupCheckboxLabel.style.marginLeft = '5px';
rmOnlyRealDupCheckboxLabel.style.fontFamily = 'sans-serif';
rmOnlyRealDupCheckboxLabel.style.fontSize = 'medium';
rmOnlyRealDupCheckboxLabel.setAttribute('for', 'rmOnlyRealDupCheckbox');
rmOnlyRealDupCheckboxLabel.textContent = 'Only compare real duplicates?';
// Create a container element to hold the main button and checkbox
var buttonContainer = document.createElement('div');
buttonContainer.id = 'buttonContainer';
buttonContainer.style.position = 'absolute';
buttonContainer.style.top = '370px';
buttonContainer.style.left = '50%';
buttonContainer.style.transform = 'translate(-50%, -50%)';
buttonContainer.appendChild(runButton);
buttonContainer.appendChild(rmOnlyRealDupCheckboxLabel);
buttonContainer.appendChild(rmOnlyRealDupCheckbox);
const metaElement = document.querySelector('.content-top-has-nav');
metaElement.insertAdjacentElement('afterend', buttonContainer);
}
// Automated duplicate scrobble deletion and navigation logic
if(shouldMatchPage()) {
window.addEventListener('load', function () {
// Event listener for when the page has finished loading
if (runScript === 'true') {
var found = 0;
var sections = Array.from(document.getElementsByTagName("tbody"));
sections.forEach(function (section) {
// Loop through each section
var els = Array.from(section.rows);
var names = els.map(function (el) {
var nmEl = el.querySelector('.chartlist-name');
var artEl = el.querySelector('.chartlist-artist');
if (rmOnlyRealDup) {
var tstEl = el.querySelector('.chartlist-timestamp');
// Construct the name string including track name, artist and timestamp
return nmEl && artEl && tstEl && nmEl.textContent.replace(/\s+/g, ' ').trim()+':'+artEl.textContent.replace(/\s+/g, ' ').trim()+':'+tstEl.textContent.replace(/\s+/g, ' ').trim();
} else {
// Construct the name string including only track name and artist
return nmEl && artEl && nmEl.textContent.replace(/\s+/g, ' ').trim()+':'+artEl.textContent.replace(/\s+/g, ' ').trim();
}
});
names.forEach(function (name, i, names) {
// Loop through each name in the section
if (!names.slice(i + 1, i + 1 + parseInt(num)).includes(name)) return;
// Check if the current name has duplicates within the specified range
var delBtn = els[i].querySelector('[data-ajax-form-sets-state="deleted"]');
if (delBtn) { delBtn.click(); found++; };
// If a delete button is found, click it and increment the counter
});
});
if (found > 0) {
// If duplicates were found, reload the page every 5 seconds
setInterval(function() {
location.reload();
}, 5000);
}
// If the URL contains "delete", go back one page in history
if (window.location.toString().includes("delete")) history.go(-1);
if (currentPage <= stopAtPage) {
// Stop the Script and message user
localStorage.setItem('runScript', 'false');
alert("LastFM duplicate deletion complete.");
location.reload();
}
// Go up one page
else window.location.href = window.location.pathname + replaceQueryParam('page', extractQueryParam('page', window.location.href) - 1, window.location.search);
}
});
}
})();
@RoelJanssens
Copy link

My library is 411 pages, must I run your script on ALL those pages? Or is it possible to run it against my library all at once?

@muhdiboy
Copy link
Author

@RoelJanssens If you want to run the script through your whole library, that's what I understood, you start the script at page 411 and let it run automatically. It will finish by itself after some time. I will add that note to the Readme.

@RoelJanssens
Copy link

Awesome! I ran script starting at the last page from my library and this way ALL duplicates were removed, thank you :)

@Aethraaa
Copy link

Aethraaa commented Feb 1, 2022

Hey maybe i'm just dumb but you said you finished the how-to but, i don't see it, where is it?

@muhdiboy
Copy link
Author

muhdiboy commented Feb 3, 2022

@Aethraaa
Copy link

i still couldn't figure it out, if it's possible can you make a tutorial video?

@muhdiboy
Copy link
Author

For everyone following this Gist, please update the script (with the updated How-to).

@Aethraaa, I've made it easier to install the script. Just click on the link in the How-to under How to install the script.
Let me know if you have issues with the rest of the usage.

@leahrr
Copy link

leahrr commented Mar 7, 2022

I leave mine on for a bit and I have years and years of scrobbles to delete and I come back and it didn't actually delete anything, its moving from page to page without doing anything

@muhdiboy
Copy link
Author

muhdiboy commented Mar 8, 2022

Hi @leahrr,

thank you for your feedback. I will look into this tomorrow. Until then please make sure that you really have duplicates and are using the latest version of the script. Also please tell me your Browser with version number.

@leahrr
Copy link

leahrr commented Mar 8, 2022

I have visible duplicates
I am using Chrome version 99.0.4844.51

@muhdiboy
Copy link
Author

muhdiboy commented Mar 11, 2022

@leahrr I couldn't reproduce it. Maybe you are not logged in? I'm sorry though. Maybe try out a different browser without the possibility of conflicting add-ons.

@alxjms92
Copy link

alxjms92 commented Apr 2, 2022

Thanks for bringing this to the level it's at, muhdiboy. I'm sure there are untold many out there who appreciate it.

I used a manual version last year that worked fine. Using this on my usual browser (Edge) did nothing: moved quickly through the pages but deleted nothing (99.0.1150.55 (Official build) (64-bit)).

Tried on a fresh install of Chrome and the same behaviour: moves quick but deletes nothing (Version 100.0.4896.60 (Official Build) (64-bit)).

Hope this isn't too difficult to figure out because it's well beyond me to understand (I am a manual labourer). Even if you don't, thanks for the effort thus far!

@Eiron
Copy link

Eiron commented Apr 6, 2022

I made a couple of tweaks below.

One is to include an EventListener for the page load (lines 76 & 108).

The other is to also include the '.chartlist-timestamp' (lines 82-83).

In theory, this should wait for the page to load completely before checking for things to delete, and not remove those single-track binge sessions where you genuinely have repeated a song. It should catch, then, only those identical scrobbles with the same timestamp.

Code
// ==UserScript==
// @name         LastFM automated duplicate scrobble deletion script
// @namespace    https://gist.github.com/muhdiboy
// @version      1.1-u5
// @downloadURL  https://gist.github.com/muhdiboy/a293cbff355af750e3b8f45ec816d1f1/raw/lastfm-automated-remove-duplicates.user.js
// @updateURL    https://gist.github.com/muhdiboy/a293cbff355af750e3b8f45ec816d1f1/raw/lastfm-automated-remove-duplicates.user.js
// @description  Based on https://gist.github.com/sk22/39cc280840f9d82df574c15d6eda6629#gistcomment-3046698, thanks to previous contributors: @CennoxX, @mattsson, @gms8994, @huw and @sk22
// @author       muhdiboy
// @include      /^https:\/\/www\.last\.fm\/user\/.*\/library.*$/
// @exclude      /^https:\/\/www\.last\.fm\/user\/.*\/library\/(albums|artists|tracks).*$/
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    var found = 0;
    var num = 5;
    var sections = Array.from(document.getElementsByTagName("tbody"));

    function exit( status ) {
        // source: https://web.archive.org/web/20091002005151/http://kevin.vanzonneveld.net:80/techblog/article/javascript_equivalent_for_phps_exit
        // http://kevin.vanzonneveld.net
        // +   original by: Brett Zamir (http://brettz9.blogspot.com)
        // +      input by: Paul
        // +   bugfixed by: Hyam Singer (http://www.impact-computing.com/)
        // +   improved by: Philip Peterson
        // +   bugfixed by: Brett Zamir (http://brettz9.blogspot.com)

        var i;

        if (typeof status === 'string') {
            alert(status);
        }

        window.addEventListener('error', function (e) {e.preventDefault();e.stopPropagation();}, false);

        var handlers = [
            'copy', 'cut', 'paste',
            'beforeunload', 'blur', 'change', 'click', 'contextmenu', 'dblclick', 'focus', 'keydown', 'keypress', 'keyup', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'resize', 'scroll',
            'DOMNodeInserted', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument', 'DOMNodeInsertedIntoDocument', 'DOMAttrModified', 'DOMCharacterDataModified', 'DOMElementNameChanged', 'DOMAttributeNameChanged', 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'online', 'offline', 'textInput',
            'abort', 'close', 'dragdrop', 'load', 'paint', 'reset', 'select', 'submit', 'unload'
        ];

        function stopPropagation (e) {
            e.stopPropagation();
            // e.preventDefault(); // Stop for the form controls, etc., too?
        }
        for (i=0; i < handlers.length; i++) {
            window.addEventListener(handlers[i], function (e) {stopPropagation(e);}, true);
        }

        if (window.stop) {
            window.stop();
        }

        throw '';
    }

    function replaceQueryParam(param, newval, search) {
        var regex = new RegExp("([?;&])" + param + "[^&;]*[;&]?");
        var query = search.replace(regex, "$1").replace(/&$/, '');
        return (query.length > 2 ? query + "&" : "?") + (newval ? param + "=" + newval : '');
    }

    function gup( name, url ) {
        if (!url) url = location.href;
        name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
        var regexS = "[\\?&]"+name+"=([^&#]*)";
        var regex = new RegExp( regexS );
        var results = regex.exec( url );
        return results == null ? null : results[1];
    }



    window.addEventListener('load', function () {
        sections.forEach(function (section) {
            var els = Array.from(section.rows);
            var names = els.map(function (el) {
                var nmEl = el.querySelector('.chartlist-name');
                var artEl = el.querySelector('.chartlist-artist');
                var tstEl = el.querySelector('.chartlist-timestamp');
                return nmEl && artEl && tstEl && nmEl.textContent.replace(/\s+/g, ' ').trim()+':'+artEl.textContent.replace(/\s+/g, ' ').trim()+':'+tstEl.textContent.replace(/\s+/g, ' ').trim();
            });

            names.forEach(function (name, i, names) {
                if (!names.slice(i + 1, i + 1 + num).includes(name)) return;
                var delBtn = els[i].querySelector('[data-ajax-form-sets-state="deleted"]');
                if (delBtn) { delBtn.click(); found++; };
            });
        });


        if (window.location.toString().includes("delete")) {
            history.go(-1);
            exit();
        }

        if (found > 0) {
            setInterval(function() {
                location.reload();
            }, 5000);
        }

        if (window.location.toString().includes("page=-")) alert("LastFM duplicate deletion complete. Disable the UserScript!");

        else window.location.href = window.location.pathname + replaceQueryParam('page', gup('page', window.location.href) - 1, window.location.search);
    });

})();

@muhdiboy
Copy link
Author

muhdiboy commented Apr 7, 2022

Hello @Eiron ,

thank you very much for your input! It makes the script much more stable.
I released a new version with your additions and also added a variable to enable or disable removing only true duplicates. The reason for my addition is because I have a bug with my wireless earbuds that seem to play the last track on loop while they are not being used. The option is there for people who want to change the (now) default behaviour.

@alxjms92, also thank you for your feedback. I'm still not sure why that happens, as I've tested the script on fresh Firefox and Chrome installations. Maybe this new addition will be helpful. Test it out and report your experience please. Thank you!

@Eiron
Copy link

Eiron commented Apr 7, 2022

@muhdiboy: I had the same experience as @leahrr and @alxjms92. It is what prompted me to make the adjustments above. Hopefully, this new version will work for them!

Edit: Should also add that I really appreciate this tool. Removed over 1000 duplicates from over the years in a matter of minutes - it would have been an impossible task else.

@noChillGrandma
Copy link

April 30th, 2022... this worked like a charm! Thank you for making this!!!

@muhdiboy
Copy link
Author

muhdiboy commented Jun 7, 2022

I used it again today and noticed a warning from Tampermonkey about Chromium moving to Manifest V3 next year. So I changed the include parameter to match. Also some small changes to the comments and arrangement in the script.

Also I noticed that the script didn't run well and stopped after a few reloads when the tab ran in background. This didn't happen before and might be because of other running websites (Youtube, Twitch). Moving the tab to a seperate window solved the issue. So just a suggestion if people have the same difficulty to move the script running tab to a seperate window.

@muhdiboy
Copy link
Author

Hi everyone,

I've added many improvements to the script and updated the Readme. A button to start the script along with prompts to customize the script has been added.
Be sure to test it out. 😁

@bloodclot
Copy link

This does not work for me on a clean install of Chrome and FFX. This is what happens on chrome when any number of pages are entered in https://imgur.com/a/MzeToEo

On FFX, the button never appears

@muhdiboy
Copy link
Author

muhdiboy commented Jun 23, 2023

Thank you for providing feedback and putting in the effort to setup a new environment.

If I didn't see wrong you've put in page 5 to stop the script at while being on page 2. That won't work, because the script will stop when hitting that page number or when it has already passed it.

So a working example would be starting at page 5, enter a number lower than the current page, for example 3, and the script will go until it will be at page 3.

I‘ll look into why the button doesn't appear on Firefox, however it did on my testing before publishing.

@muhdiboy
Copy link
Author

I've updated the Gist to version 1.4 with many changes and a complete overhaul including a new checkbox, a Cancel button, prompts catching wrong input and many more things working in the background to make it more stable, reliable and easier to use.
It is no longer necessary to modify the script, everything is done via the buttons/checkbox.

However, I can confirm, that Firefox isn't behaving the same as Chromium browsers. It often stops after one or two reloads. Manual refresh can help it go further, but that is not efficient.
The Readme has also been updated accordingly.

Feel free to test it out and post your feedback.

@muhdiboy
Copy link
Author

Just a small info: Because of Last.fm's dynamic page design the buttons may appear in the wrong pages. This occurs sometimes when changing e.g. from "Library" to "Artists". Pressing them won't do anything because the script will always check before doing something. Also reloading the page will always remove them, if they shouldn't be there.

@bmrs
Copy link

bmrs commented Jul 31, 2023

Hi,

Thank you for this very enjoyable script. The process is not very smooth on my library with 160,000+ scrobbles on 3267 pages: it stops after a dozen of pages each time. It is running in a separate window from the last page to the page #1. Help appreciated!

@muhdiboy
Copy link
Author

I can confirm, that the script sometimes has issues to load the next page. I think the issue might be because the event listener (`window.addEventListener('load', function ()`, [line 178](https://gist.github.com/muhdiboy/a293cbff355af750e3b8f45ec816d1f1#file-lastfm-automated-remove-duplicates-user-js-L178)) is currently getting loaded after a few checks and inside a function, which might add a miniscule amount of delay.

To test this I added a timeout of 3-5 seconds to the listener, which would result in no reloads. After lowering it to 250 ms the issue could be reproduced more frequently and would almost disappear when reducing it to 25 ms.

I also checked how long it needs to reach the listener and measured ~200 ms. After that it is waiting for the page to load and that takes ~50-100 ms. That means that the script only has ~50 ms of time to reach that point, otherwise it will not run because the listener won't trigger as the website has already been loaded. So I have to minimize that 200 ms time window.

Fixed it by untying the run function from the URL checker function, et voilà, the script is reaching that spot instantly and only has to wait for the webpage to load which triggers the event listener.


Hi @bmrs, thank you for noticing and giving feedback. Please try again with the new version (1.4.3). Enjoy!

@bmrs
Copy link

bmrs commented Sep 25, 2023

Hi @bmrs, thank you for noticing and giving feedback. Please try again with the new version (1.4.3). Enjoy!

Yes, it works well now, and way faster… Thank you!

@reticivis-net
Copy link

Found a bug. On line 201, you add "num", but num is a string and needs to be cast to an int, or else it's concatenated, not added.

@pleebus
Copy link

pleebus commented Nov 28, 2023

hey dude, loving this script, is helping to do something I thought I was going to have to do semi-manually...

however, I'm hitting the "405 - Method Not Allowed" error every few pages. is this just because my last.fm history is so huge (4,000+ pages going back almost 20 years) with a ridiculous amount of duplicates? I had some bug a few years ago that caused some tracks to be scrobbled literally 50+ times for a single play and have only recently decided to do something about that. Thought this could be the solution, and it's working, but it's also not so stable and crashes after maximum 60 seconds each time I run it. am I doing something wrong, or is it just the size of the task I'm asking it to do?!

EDIT: If it helps to add some more context, it does seem that the redirect to /library/delete happens whenever it hits a block of duplicate songs. I'm using a fresh install of Brave that I downloaded just to run this script. But it's also happening in a fresh Chrome install.

@Jeboose
Copy link

Jeboose commented Dec 27, 2023

hey dude, loving this script, is helping to do something I thought I was going to have to do semi-manually...

however, I'm hitting the "405 - Method Not Allowed" error every few pages. is this just because my last.fm history is so huge (4,000+ pages going back almost 20 years) with a ridiculous amount of duplicates? I had some bug a few years ago that caused some tracks to be scrobbled literally 50+ times for a single play and have only recently decided to do something about that. Thought this could be the solution, and it's working, but it's also not so stable and crashes after maximum 60 seconds each time I run it. am I doing something wrong, or is it just the size of the task I'm asking it to do?!

EDIT: If it helps to add some more context, it does seem that the redirect to /library/delete happens whenever it hits a block of duplicate songs. I'm using a fresh install of Brave that I downloaded just to run this script. But it's also happening in a fresh Chrome install.

You're using tampermonkey with lastfm pro? I'm having trouble getting this script to do much of anything it seems to just endlessly scroll through my scrobbles without actually removing any. Tried firefox, chrome, and brave with no results.

@muhdiboy
Copy link
Author

muhdiboy commented Jan 9, 2024

@reticivis-net

Found a bug. On line 201, you add "num", but num is a string and needs to be cast to an int, or else it's concatenated, not added.

Thank you, I updated it to parse it to int. Version 1.4.4 onwards includes this change.


@pleebus

am I doing something wrong, or is it just the size of the task I'm asking it to do?!
I'm using a fresh install of Brave that I downloaded just to run this script. But it's also happening in a fresh Chrome install.

Sorry, that the script didn't work for you as expected. I've tried on multiple installations (fresh/old + Windows/Ubuntu(Flatpak, DEB)) and couldn't seem to reproduce the mentioned errors.

EDIT: If it helps to add some more context, it does seem that the redirect to /library/delete happens whenever it hits a block of duplicate songs.

Yes, after some retesting, that was what I've experienced too. And it was just that the line "if (window.location.toString().includes("delete")) history.go(-1);" was not in the right place.
Now it should go back in history and retry, if it hits the "405 - Methos Not Allowed" Page. Tested again after this change and it ran flawlessly again.

Change is included in version 1.4.5

@muhdiboy
Copy link
Author

muhdiboy commented Jan 9, 2024

@Jeboose
Can you retest with the newest version? If you have a different issue, please retest on a single page where you are sure multiple duplicates exist and report your findings. I will try to help as possible.

Just to be sure, if you didn't know the feature: the script is differentiating duplicates, ones that are scrobbled at exactly the same time (to the minute) and ones where you can choose the amount to be checked. First one is the true duplicate, enabled by the checkmark. Second one is active when the checkmark is unchecked. When unchecked, another input box will appear after hitting the "Remove duplicates" button. There you can input the number of scrobbles to compare to.
This is described in the doc above: Using the script - step by step

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment