Skip to content

Instantly share code, notes, and snippets.

@holly-hacker
Created December 19, 2018 11:49
Show Gist options
  • Save holly-hacker/174a61c526dff4c166f9c20f8eab7054 to your computer and use it in GitHub Desktop.
Save holly-hacker/174a61c526dff4c166f9c20f8eab7054 to your computer and use it in GitHub Desktop.
KHInsider downloader
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace KHDownload
{
internal static class Program
{
private static readonly Regex RegexJs = new Regex(@"<script>(eval[\s\S]*?)<\/script>\s+<h2>(.*?)</h2>");
private static readonly Regex RegexParams = new Regex(@"}\('(.+)',(\d+),(\d+),'([^']+)'\.split\('\|'\)");
private static readonly Regex RegexTracks = new Regex(@"mediaPath=\\'(.*?)\\',extension=\\'\\',tracks=\[(.*?),\],trackCount=");
private static void Main()
{
while (true) {
Console.Write("URL: ");
string url = Console.ReadLine();
DoDownload(url);
Console.WriteLine("Finished downloading.");
Console.WriteLine();
}
}
private static void DoDownload(string url)
{
string albumSource = new WebClient().DownloadString(url);
var matchJs = RegexJs.Match(albumSource);
string js = matchJs.Groups[1].Value.Trim().TrimEnd(';').TrimEnd();
string albumName = matchJs.Groups[2].Value;
var args = RegexParams.Match(js); // could pass RegexParams immediately
string obfCode = args.Groups[1].Value;
int keyspace = int.Parse(args.Groups[2].Value);
int stringsLen = int.Parse(args.Groups[3].Value);
string[] strings = args.Groups[4].Value.Split('|');
Debug.Assert(keyspace == 62);
Debug.Assert(stringsLen == strings.Length);
var unpackedJs = Unpack(obfCode, strings);
var matchTracks = RegexTracks.Match(unpackedJs);
string mediaPath = matchTracks.Groups[1].Value;
string tracksJson = "[" + matchTracks.Groups[2].Value + "]";
var tracks = JsonConvert.DeserializeObject<TrackModel[]>(tracksJson);
Console.WriteLine($"Found {tracks.Length} tracks");
Directory.CreateDirectory(albumName);
Parallel.ForEach(tracks, tr =>
{
string fileName = tr.Track + ". " + WebUtility.HtmlDecode(tr.Name);
if (!fileName.EndsWith(".mp3")) fileName += ".mp3";
string filePath = Path.Combine(albumName, fileName);
if (File.Exists(filePath)) {
Console.WriteLine($"Skipping {fileName} because it already exists!");
} else {
new WebClient().DownloadFile(mediaPath + tr.File, filePath);
Console.WriteLine($"Downloaded {fileName}");
}
});
}
private static string Unpack(string codeParam, string[] strings)
{
var dic = new Dictionary<string, string>();
for (int i = strings.Length - 1; i >= 0; i--) {
string key = Base62(i);
dic[key] = !string.IsNullOrEmpty(strings[i]) ? strings[i] : key;
}
return Regex.Replace(codeParam, @"\b\w+\b", match => dic[match.Value], RegexOptions.ECMAScript);
string Base62(int c)
{
const string keyspace = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
string recursive = c < keyspace.Length ? string.Empty : Base62(c / keyspace.Length);
return recursive + keyspace[c % keyspace.Length];
}
}
private class TrackModel
{
public string Track, Name, Length, File;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment