khinsider Batch Downloader

發表於
分類於 userscript

This article is automatically translated by LLM, so the translation may be inaccurate or incomplete. If you find any mistake, please let me know.
You can find the original article here .

khinsider is a website that offers many game OST downloads and previews, as well as many free single track downloads. However, the feature to download entire albums requires a donation, so I created a simple script for batch downloading. Download link: Greasy Fork

I discovered this website to download the entire OST of Eternal Senia.

This game is also quite good, and you might want to give it a try.

Usage

After installing the script above, go to the khinsider website and find the game you like. Using Eternal Senia as an example:

Eternal Senia OST page

Click on click to download in the image, and a progress bar will appear on the right. It is recommended to keep the browser on this tab, as browsing other tabs simultaneously might cause lag (Firefox).

Album Downloading

Once the progress bar is full, the browser should automatically trigger the download. The file name will be [game name].zip. Open the zip file with programs like 7zip or winrar, and you will find the music you want inside.

zip

Principle

Clicking on any music track below will take you to the music download page, where there is a link. Using ajax, the file is stored in memory as a Blob format, and finally packaged into a zip file using JSZip.

Code: Greasy Fork

downloadblob

Since the website itself is on https://downloads.khinsider.com/, which is https, and the server storing the music is on http://66.90.93.122/, which is http, the different protocols do not comply with the same-origin policy, so it is not possible to use the usual XMLHttpRequest and fetch. The GM_xmlhttpRequest provided by the script manager is needed to make the request.

The function is also simple, just provide the url and return a Promise<Blob>.

// @grant        GM_xmlhttpRequest

//something...

function downloadblob(url) {
	return new Promise((resolve, reject) => {
		GM_xmlhttpRequest({
			method: 'GET',
			url,
			responseType: 'blob',
			onload: res => resolve(res.response)
		})
	})
}

Main Program

Bind the click event to the existing click to download link and cancel it, then display the progress bar. Next, get the game name and the URLs of the download pages for each song. Use fetch to grab the download pages, and use jquery to find the real mp3 links on them. Then convert the real links into {blob: Promise<Blob>, name: String} objects, and use reduce to add them to JSZip (JSZip supports Promise).

JSZip supports progress updates, so update the <progress> element when there is progress. Finally, when the download is complete, create a URL from the entire zip Blob and trigger the download to complete it.

$('a:contains("click to download")').on('click', e => {
	e.preventDefault()
	$('.albumMassDownload').append(`
<div>
<span>Download progress:</span>
<progress min="0" max="100" id="dp" value="0"></progress>
</div>
`)

	const title = $('h2')[0].textContent
	const urls = $('tr>td.clickable-row:not([align])')
		.toArray()
		.map(el =>
			$(el)
				.find('a')
				.attr('href')
		)
	const requests = urls.map(e => fetch(e).then(r => r.text()))
	Promise.all(requests).then(ar =>
		ar
			.map(ht => {
				const url = $(ht)
					.find('a:contains("Click here to download as MP3")')
					.attr('href')
				return {
					blob: downloadblob(url),
					name: decodeURIComponent(url.split('/').pop())
				}
			})
			.reduce((zip, file) => {
				zip.file(file.name, file.blob)
				return zip
			}, new JSZip())
			.generateAsync({ type: 'blob' }, meta => {
				$('#dp').attr('value', parseInt(meta.percent))
			})
			.then(blob => {
				const url = URL.createObjectURL(blob)
				const a = document.createElement('a')
				a.download = title + '.zip'
				a.href = url
				document.body.appendChild(a)
				a.click()
				a.remove()
				URL.revokeObjectURL(url)
			})
	)
})