Skip to content

Instantly share code, notes, and snippets.

@Kefta
Last active September 29, 2021 11:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kefta/558ce995ac409a39b9950f2cd6282b3a to your computer and use it in GitHub Desktop.
Save Kefta/558ce995ac409a39b9950f2cd6282b3a to your computer and use it in GitHub Desktop.
Run in your devtool snippets on chrome while on the DomainLink
// Overengineered script to remove items from an RYM wishlist
const DomainLink = "rateyourmusic.com" // Base domain link
const WishlistLinkPath = "/collection/username/wishlist,ss.d"; // Link to the wishlist
const MinPageNum = 1; // inclusive, <1 == 1
const MaxPageNum = 0; // inclusive, <1 == Max wishlist page
const MinRequestTime = 8000; // ms
const MaxRequestTime = 10000;
// Function that will be called for printing, takes one string argument
// Defining this as function(){} will silence all messages except for thrown exceptions
const OutputFunc = console.log;
const HaltOnNonCriticalError = true; // If true, throws exceptions even for non-critical errors
function Run()
{
DeleteWishlist(WishlistLinkPath, MinPageNum, MaxPageNum, rym.session.token, MakeOutputFunc(OutputFunc, HaltOnNonCriticalError));
}
/////////////////////////////////////////////////////////////
{
function Strip(sLink)
{
var aLink = sLink.match(/^([\w+]+:\/\/)?(\w+\.)?(.+?\.\w*)/);
if (aLink === null) throw `invalid link "${sLink}"`;
return aLink[3];
}
if (Strip(window.location.href) !== Strip(DomainLink)) throw `the script must be executed while on ${DomainLink}`;
}
const OutputTypes = {
Begin: 0,
Complete: 1,
Wait: 2,
Load: 3,
LoadError: 4,
Page: 5,
PageError: 6,
Release: 7,
ReleaseError: 8
}
function MakeOutputFunc(fSimpleOutput, bHaltingLoadErrors = false)
{
return function(eOutputType, sMsg)
{
var sPrefix = "UNKNOWN";
switch(eOutputType)
{
case OutputTypes.Begin:
case OutputTypes.Complete: sPrefix = "WISHLIST"; break;
case OutputTypes.Wait: sPrefix = "WAIT"; break;
case OutputTypes.Load: sPrefix = "LOAD"; break;
case OutputTypes.LoadError: sPrefix = "LOAD ERROR"; break;
case OutputTypes.Page: sPrefix = "PAGE"; break;
case OutputTypes.PageError: sPrefix = "PAGE ERROR"; break;
case OutputTypes.Release: sPrefix = "RELEASE"; break;
case OutputTypes.ReleaseError: sPrefix = "RELEASE ERROR"; break;
}
const sOutput = `[${sPrefix}] ${sMsg}`;
if (bHaltingLoadErrors &&
(eOutputType == OutputTypes.LoadError
|eOutputType == OutputTypes.PageError
|eOutputType == OutputTypes.ReleaseError))
throw sOutput;
fSimpleOutput(sOutput);
}
}
function GetRequestTime()
{
// FIXME: Placeholder
return Math.floor(Math.random() * (MaxRequestTime - MinRequestTime) + MinRequestTime);
}
function MakeFetchFailureFunc(fOutput, sLink, bHaltingLoadErrors = false)
{
return function(error)
{
const sOutput = `failed to load page "${sLink}" with error "${error}"`;
if (bHaltingLoadErrors)
throw sOutput;
fOutput(OutputTypes.LoadError, sOutput);
}
}
function Wait(fOutput, fCallback, ...args)
{
const unDelay = GetRequestTime();
fOutput(OutputTypes.Wait, `${unDelay / 1000} seconds`);
setTimeout(fCallback, unDelay, ...args);
}
function ParseMax(sPage)
{
// FIXME: Skip escaped quotes inside the href string
const aMatches = sPage.match(/<a\s+class\s*=\s*("navlinknum")(?!.*\1)\s+href\s*=\s*"[^"]*">\s*(\d+)\s*</);
if (aMatches === null) throw "failed to find max page number (navlinknum element), make sure the wishlist link is correct";
const uMaxPageNum = parseInt(aMatches[2], 10);
if (uMaxPageNum !== uMaxPageNum) throw `failed to convert max page string "${aMatches[2]}" into number`;
if (uMaxPageNum < 1) throw `invalid parsed max page number $uMaxPageNum`;
return uMaxPageNum;
}
function UnwishlistItem(sReleaseLinkPath, sID, sToken, fOutput)
{
fOutput(OutputTypes.Release, `unwishlisting item "${sReleaseLinkPath}" (${sID})`);
//rym.request.post("CatalogSetOwnership", { type: 'l', assoc_id: sID, ownership: 'n' });
var data = new FormData();
data.append("type", "l");
data.append("assoc_id", sID);
data.append("ownership", "n");
data.append("action", "CatalogSetOwnership")
data.append("rym_ajax_req", 1);
data.append("request_token", sToken);
fetch("/httprequest/CatalogSetOwnership", {method: "POST", body: data})
.then(response => fOutput(OutputTypes.Release, `unwishlisted item "${sReleaseLinkPath}" (${sID}) with status ${response.status}`))
.catch(error => fOutput(OutputTypes.ReleaseError, `failed to unwishlist item "${sReleaseLinkPath}" (${sID}) with error ${error}`));
}
function ForEachItem(sWishlistLinkPath, sToken, fOutput, uCurPage, uPageCount, aReleases)
{
const tReleaseData = aReleases.pop();
const sReleaseLinkPath = tReleaseData[0];
const sID = tReleaseData[1];
UnwishlistItem(sReleaseLinkPath, sID, sToken, fOutput);
if (aReleases.length !== 0)
Wait(fOutput, ForEachItem, sWishlistLinkPath, sToken, fOutput, uCurPage, uPageCount, aReleases);
else if (uPageCount !== 0)
Wait(fOutput, ForEachPage, sWishlistLinkPath, sToken, fOutput, uCurPage, uPageCount);
else
fOutput(OutputTypes.Complete, "completed wishlist deletion");
}
function ForEachPage(sWishlistLinkPath, sToken, fOutput, uCurPage, uPageCount)
{
const sPageLinkPath = sWishlistLinkPath + "/" + uCurPage;
fetch(sPageLinkPath, {method: "GET"})
.then(response => response.text())
.then(function(sPage)
{
// FIXME: Handle blank page
fOutput(OutputTypes.Load, `loaded page ${uCurPage}`);
// FIXME: Very lazy, will match to the next a tag outside of this tr if the pattern doesn't fit as expected
const rReleases = /<a\s+title\s*=\s*"\[(Album|Film)\d+\]"\s*href\s*=\s*"([^"]+)"[\s\S]+?"tagl(\d+)"/g;
var aReleases = []
var tReleaseData
while (tReleaseData = rReleases.exec(sPage))
{
aReleases.push([tReleaseData[2], tReleaseData[3]]);
}
if (aReleases.length == 0)
{
fOutput(OutputTypes.PageError, `page ${uCurPage} is empty or matching failed`);
Wait(fOutput, ForEachPage, sWishlistLinkPath, sToken, fOutput, uCurPage - 1, uPageCount - 1);
}
else
{
fOutput(OutputTypes.Page, `processing page ${uCurPage}`);
Wait(fOutput, ForEachItem, sWishlistLinkPath, sToken, fOutput, uCurPage - 1, uPageCount - 1, aReleases);
}
})
.catch(MakeFetchFailureFunc(fOutput, sPageLinkPath));
}
function DeleteWishlist(sWishlistLinkPath, uMinPageNum, uMaxPageNum, sToken, fOutput)
{
const aWishlistLinkPath = sWishlistLinkPath.match(/^(.+)[\\\/]*$/);
if (aWishlistLinkPath === null) throw `invalid wishlist link path "${sWishlistLinkPath}"`;
sWishlistLinkPath = aWishlistLinkPath[1];
uMinPageNum = uMinPageNum = uMinPageNum < 1 ? 1 : Math.floor(uMinPageNum);
uMaxPageNum = Math.floor(uMaxPageNum);
if (uMaxPageNum < 1)
{
fetch(sWishlistLinkPath, {method: "GET"})
.then(response => response.text())
.then(function(sPage)
{
uMaxPageNum = ParseMax(sPage);
if (uMinPageNum > uMaxPageNum) throw `MinPageNum (${uMinPageNum}) is greater than the number of pages left in the wishlist (${uMaxPageNum})`;
fOutput(OutputTypes.Begin, `beginning wishlist deletion for pages ${uMinPageNum} to ${uMaxPageNum}`);
// FIXME: Handle having no wishlist pages
Wait(fOutput, ForEachPage, sWishlistLinkPath, sToken, fOutput, uMaxPageNum, uMaxPageNum - uMinPageNum + 1);
})
.catch(MakeFetchFailureFunc(fOutput, sWishlistLinkPath));
}
else
{
if (uMinPageNum > uMaxPageNum) throw `MinPageNum (${uMinPageNum}) is greater than MaxPageNum (${uMaxPageNum})`;
fOutput(OutputTypes.Begin, `beginning wishlist deletion for pages ${uMinPageNum} to ${uMaxPageNum}`);
ForEachPage(sWishlistLinkPath, sToken, fOutput, uMaxPageNum, uMaxPageNum - uMinPageNum + 1);
}
}
/////////////////////////////////////////////////////////////
Run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment