mirror of
https://github.com/knah/VRCMelonAssistant.git
synced 2024-06-14 00:14:31 +12:00
Playlist downloading
This commit is contained in:
parent
07c2d90b09
commit
e7dbe58025
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using static ModAssistant.Http;
|
using static ModAssistant.Http;
|
||||||
|
@ -13,35 +15,108 @@ namespace ModAssistant.API
|
||||||
private static readonly string CustomSongsFolder = Path.Combine("Beat Saber_Data", "CustomLevels");
|
private static readonly string CustomSongsFolder = Path.Combine("Beat Saber_Data", "CustomLevels");
|
||||||
private const bool BypassDownloadCounter = false;
|
private const bool BypassDownloadCounter = false;
|
||||||
|
|
||||||
public static async Task GetFromKey(string Key, bool showNotification = true)
|
public static async Task<BeatSaverMap> GetFromKey(string Key, bool showNotification = true)
|
||||||
{
|
{
|
||||||
BeatSaverApiResponse Map = await GetResponse(BeatSaverURLPrefix + "/api/maps/detail/" + Key);
|
return await GetMap(Key, "key", showNotification);
|
||||||
await InstallMap(Map, showNotification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task GetFromHash(string Hash, bool showNotification = true)
|
public static async Task<BeatSaverMap> GetFromHash(string Hash, bool showNotification = true)
|
||||||
{
|
{
|
||||||
BeatSaverApiResponse Map = await GetResponse(BeatSaverURLPrefix + "/api/maps/by-hash/" + Hash);
|
return await GetMap(Hash, "hash", showNotification);
|
||||||
await InstallMap(Map, showNotification);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<BeatSaverApiResponse> GetResponse(string url)
|
private static async Task<BeatSaverMap> GetMap(string id, string type, bool showNotification)
|
||||||
{
|
{
|
||||||
|
string urlSegment;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "hash":
|
||||||
|
urlSegment = "/api/maps/by-hash/";
|
||||||
|
break;
|
||||||
|
case "key":
|
||||||
|
urlSegment = "/api/maps/detail/";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeatSaverMap map = new BeatSaverMap();
|
||||||
|
map.Success = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var resp = await HttpClient.GetAsync(url);
|
BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id);
|
||||||
var body = await resp.Content.ReadAsStringAsync();
|
map.Name = await InstallMap(beatsaver.map, showNotification);
|
||||||
|
map.Success = true;
|
||||||
return JsonSerializer.Deserialize<BeatSaverApiResponse>(body);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e);
|
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e}", "ERROR");
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true)
|
||||||
|
{
|
||||||
|
BeatSaverApiResponse response = new BeatSaverApiResponse();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var resp = await HttpClient.GetAsync(url);
|
||||||
|
response.statusCode = resp.StatusCode;
|
||||||
|
response.ratelimit = GetRatelimit(resp.Headers);
|
||||||
|
if (response.statusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
if (response.ratelimit.IsSafe)
|
||||||
|
{
|
||||||
|
string body = await resp.Content.ReadAsStringAsync();
|
||||||
|
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (showNotification)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task InstallMap(BeatSaverApiResponse Map, bool showNotification = true)
|
private static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
|
||||||
|
{
|
||||||
|
BeatSaverRatelimit ratelimit = new BeatSaverRatelimit();
|
||||||
|
|
||||||
|
var _rateLimitRemaining = headers.GetValues("Rate-Limit-Remaining").GetEnumerator();
|
||||||
|
var _rateLimitReset = headers.GetValues("Rate-Limit-Reset").GetEnumerator();
|
||||||
|
var _rateLimitTotal = headers.GetValues("Rate-Limit-Total").GetEnumerator();
|
||||||
|
_rateLimitRemaining.MoveNext();
|
||||||
|
_rateLimitReset.MoveNext();
|
||||||
|
_rateLimitTotal.MoveNext();
|
||||||
|
ratelimit.Remaining = Int32.Parse(_rateLimitRemaining.Current);
|
||||||
|
ratelimit.Reset = Int32.Parse(_rateLimitReset.Current);
|
||||||
|
ratelimit.Total = Int32.Parse(_rateLimitTotal.Current);
|
||||||
|
ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset);
|
||||||
|
|
||||||
|
return ratelimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime UnixTimestampToDateTime(double unixTime)
|
||||||
|
{
|
||||||
|
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
|
||||||
|
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
|
||||||
|
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true)
|
||||||
{
|
{
|
||||||
string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip";
|
string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip";
|
||||||
string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})")
|
string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})")
|
||||||
|
@ -83,14 +158,54 @@ namespace ModAssistant.API
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed");
|
if (showNotification)
|
||||||
string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues");
|
{
|
||||||
string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle");
|
string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed");
|
||||||
MessageBox.Show($"{line1}\n{line2}", title);
|
string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues");
|
||||||
|
string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle");
|
||||||
|
MessageBox.Show($"{line1}\n{line2}", title);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BeatSaverMap
|
||||||
|
{
|
||||||
|
public BeatSaverApiResponse response { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BeatSaverApiResponse
|
public class BeatSaverApiResponse
|
||||||
|
{
|
||||||
|
public HttpStatusCode statusCode { get; set; }
|
||||||
|
public BeatSaverRatelimit ratelimit { get; set;}
|
||||||
|
public BeatSaverApiResponseMap map { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BeatSaverRatelimit
|
||||||
|
{
|
||||||
|
public int Remaining { get; set; }
|
||||||
|
public int Total { get; set; }
|
||||||
|
public int Reset { get; set; }
|
||||||
|
public DateTime ResetTime { get; set; }
|
||||||
|
public bool IsSafe
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Remaining > 3) return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Wait()
|
||||||
|
{
|
||||||
|
await Task.Delay(new TimeSpan(ResetTime.Ticks - DateTime.Now.Ticks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BeatSaverApiResponseMap
|
||||||
{
|
{
|
||||||
public Metadata metadata { get; set; }
|
public Metadata metadata { get; set; }
|
||||||
public Stats stats { get; set; }
|
public Stats stats { get; set; }
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static ModAssistant.Http;
|
using static ModAssistant.Http;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -44,8 +41,11 @@ namespace ModAssistant.API
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task DownloadFrom(string file, System.Windows.Controls.ProgressBar progress = null)
|
public static async Task DownloadFrom(string file, bool gui = false, System.Windows.Controls.ProgressBar progress = null)
|
||||||
{
|
{
|
||||||
|
int Errors = 0;
|
||||||
|
int Minimum = 0;
|
||||||
|
int Value = 0;
|
||||||
if (progress != null)
|
if (progress != null)
|
||||||
{
|
{
|
||||||
progress.Minimum = 0;
|
progress.Minimum = 0;
|
||||||
|
@ -53,22 +53,53 @@ namespace ModAssistant.API
|
||||||
progress.Value = 0;
|
progress.Value = 0;
|
||||||
}
|
}
|
||||||
Playlist playlist = JsonSerializer.Deserialize<Playlist>(File.ReadAllText(file));
|
Playlist playlist = JsonSerializer.Deserialize<Playlist>(File.ReadAllText(file));
|
||||||
|
int Maximum = playlist.songs.Length;
|
||||||
if (progress != null) progress.Maximum = playlist.songs.Length;
|
if (progress != null) progress.Maximum = playlist.songs.Length;
|
||||||
|
|
||||||
foreach (Playlist.Song song in playlist.songs)
|
foreach (Playlist.Song song in playlist.songs)
|
||||||
{
|
{
|
||||||
|
API.BeatSaver.BeatSaverMap response = new BeatSaver.BeatSaverMap();
|
||||||
if (!string.IsNullOrEmpty(song.hash))
|
if (!string.IsNullOrEmpty(song.hash))
|
||||||
{
|
{
|
||||||
await BeatSaver.GetFromHash(song.hash, false);
|
response = await BeatSaver.GetFromHash(song.hash, false);
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(song.key))
|
else if (!string.IsNullOrEmpty(song.key))
|
||||||
{
|
{
|
||||||
await BeatSaver.GetFromKey(song.key, false);
|
response = await BeatSaver.GetFromKey(song.key, false);
|
||||||
}
|
}
|
||||||
|
Value++;
|
||||||
if (progress != null) progress.Value++;
|
if (progress != null) progress.Value++;
|
||||||
|
|
||||||
|
if (gui)
|
||||||
|
{
|
||||||
|
if (response.Success)
|
||||||
|
{
|
||||||
|
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}";
|
||||||
|
ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash}");
|
||||||
|
await Task.Delay(3 * 1000);
|
||||||
|
Errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gui)
|
||||||
|
{
|
||||||
|
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string TextProgress(int min, int max, int value)
|
||||||
|
{
|
||||||
|
if (max == value)
|
||||||
|
{
|
||||||
|
return $" {string.Concat(Enumerable.Repeat("▒", 10))} [{value}/{max}]";
|
||||||
|
}
|
||||||
|
int interval = (int)Math.Floor((double)value / ( ((double)max - (double)min ) / (double)10));
|
||||||
|
return $" {string.Concat(Enumerable.Repeat("▒", interval))}{string.Concat(Enumerable.Repeat("░", 10 - interval))} [{value}/{max}]";
|
||||||
|
}
|
||||||
|
|
||||||
class Playlist
|
class Playlist
|
||||||
{
|
{
|
||||||
|
|
|
@ -104,6 +104,9 @@
|
||||||
<sys:String x:Key="Options:GameType:Oculus">Options:GameType:Oculus</sys:String>
|
<sys:String x:Key="Options:GameType:Oculus">Options:GameType:Oculus</sys:String>
|
||||||
<sys:String x:Key="Options:Tools">Options:Tools</sys:String>
|
<sys:String x:Key="Options:Tools">Options:Tools</sys:String>
|
||||||
<sys:String x:Key="Options:InstallPlaylist">Options:InstallPlaylist</sys:String>
|
<sys:String x:Key="Options:InstallPlaylist">Options:InstallPlaylist</sys:String>
|
||||||
|
<sys:String x:Key="Options:InstallingPlaylist">{0} Options:InstallingPlaylist</sys:String>
|
||||||
|
<sys:String x:Key="Options:FailedPlaylistSong">{0} Options:FailedPlaylistSong</sys:String>
|
||||||
|
<sys:String x:Key="Options:FinishedPlaylist">{0} {1} Options:FinishedPlaylist</sys:String>
|
||||||
<sys:String x:Key="Options:Diagnostics">Options:Diagnostics</sys:String>
|
<sys:String x:Key="Options:Diagnostics">Options:Diagnostics</sys:String>
|
||||||
<sys:String x:Key="Options:OpenLogsButton">Options:OpenLogsButton</sys:String>
|
<sys:String x:Key="Options:OpenLogsButton">Options:OpenLogsButton</sys:String>
|
||||||
<sys:String x:Key="Options:OpenAppDataButton">Options:OpenAppDataButton</sys:String>
|
<sys:String x:Key="Options:OpenAppDataButton">Options:OpenAppDataButton</sys:String>
|
||||||
|
|
|
@ -144,6 +144,9 @@
|
||||||
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
|
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
|
||||||
<sys:String x:Key="Options:Tools">Tools</sys:String>
|
<sys:String x:Key="Options:Tools">Tools</sys:String>
|
||||||
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String>
|
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String>
|
||||||
|
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String>
|
||||||
|
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String>
|
||||||
|
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String>
|
||||||
<sys:String x:Key="Options:Diagnostics">Diagnostics</sys:String>
|
<sys:String x:Key="Options:Diagnostics">Diagnostics</sys:String>
|
||||||
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
|
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
|
||||||
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>
|
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>
|
||||||
|
|
|
@ -312,9 +312,10 @@ namespace ModAssistant.Pages
|
||||||
private void InstallPlaylistButton_Click(object sender, RoutedEventArgs e)
|
private void InstallPlaylistButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
string playlistFile = Utils.GetManualFile();
|
string playlistFile = Utils.GetManualFile();
|
||||||
Utils.Log(playlistFile, "DEBUG");
|
if (File.Exists(playlistFile))
|
||||||
var result = Task.Run(async () => { API.Playlists.DownloadFrom(playlistFile).Wait(); });
|
{
|
||||||
|
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile, true).Wait(); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue