remove outdated backends

This commit is contained in:
2025-05-21 11:20:00 +02:00
parent 4bd0ddb6b8
commit 6e4f03aef8
23 changed files with 0 additions and 4317 deletions

7
backend/.gitignore vendored
View File

@@ -1,7 +0,0 @@
.vs/
bin/
obj/
*.db
*.env
cookies.json

View File

@@ -1,85 +0,0 @@
using OsuParsers.Enums.Replays;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace shitweb
{
public class ApiClient
{
private const string CookiesFilePath = "cookies.json";
private HttpClient _client;
private HttpClientHandler _handler;
public ApiClient()
{
_handler = new HttpClientHandler();
_client = new HttpClient(_handler);
}
public async Task InitializeAsync()
{
LoadCookies();
}
public void SaveCookies(String cookie)
{
var cookies = _handler.CookieContainer.GetCookies(new Uri("https://proxy.illegalesachen.download"));
var Cookie = new CookieInfo();
Cookie.Name = "session_cookie";
Cookie.Value = cookie;
var json = JsonSerializer.Serialize(Cookie, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(CookiesFilePath, json);
LoadCookies();
}
public Boolean LoadCookies()
{
if (File.Exists(CookiesFilePath))
{
var json = File.ReadAllText(CookiesFilePath);
var cookieInfo = JsonSerializer.Deserialize<CookieInfo>(json);
var cookie = new Cookie(cookieInfo.Name, cookieInfo.Value);
_handler.CookieContainer.Add(new Uri("https://proxy.illegalesachen.download"), cookie);
return true;
}
return false;
}
public async Task UpdateSettingsAsync(string endpoint)
{
var requestContent = new JsonContent(new { endpoint = endpoint });
var response = await _client.PostAsync("https://proxy.illegalesachen.download/settings", requestContent);
try
{
response.EnsureSuccessStatusCode();
}
catch (Exception ex) {
System.Console.WriteLine(ex.Message);
}
}
}
public class CookieInfo
{
public string Name { get; set; }
public string Value { get; set; }
}
public class JsonContent : StringContent
{
public JsonContent(object obj)
: base(JsonSerializer.Serialize(obj), System.Text.Encoding.UTF8, "application/json")
{
}
}
}

View File

@@ -1,298 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using shitweb;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Net.Http;
using System.Text.RegularExpressions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
app.UseCors("AllowAll");
var apiClient = new ApiClient();
app.MapGet("/ping", () => "pong");
app.MapGet("/login", () => {
return Results.Redirect("https://proxy.illegalesachen.download/login");
});
app.MapGet("/api/v1/songs/{hash}", (string hash) =>
{
return Results.Ok(new { hash });
});
app.MapGet("/api/v1/songs/recent", (HttpContext httpContext, int? limit, int? offset) =>
{
var limitValue = limit ?? 100; // default to 10 if not provided
var offsetValue = offset ?? 0; // default to 0 if not provided
return Results.Json(SqliteDB.GetByRecent(limitValue, offsetValue));
});
app.MapGet("/api/v1/songs/favorite", (int? limit, int? offset) =>
{
var limitValue = limit ?? 100; // default to 10 if not provided
var offsetValue = offset ?? 0; // default to 0 if not provided
return Results.Ok(new { Limit = limitValue, Offset = offsetValue, Message = "List of favorite songs" });
});
app.MapGet("/api/v1/songs/{hash}", (string hash) =>
{
return Results.Ok($"Details for song with hash {hash}");
});
app.MapGet("/api/v1/collections/", async (int? limit, int? offset, [FromServices] IMemoryCache cache) =>
{
var limitValue = limit ?? 100; // default to 10 if not provided
var offsetValue = offset ?? 0;
string cacheKey = $"collections_{offsetValue}_{limitValue}";
if (!cache.TryGetValue(cacheKey, out var collections))
{
collections = Osudb.Instance.GetCollections(limit: limitValue, offset: offsetValue);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromDays(1))
.SetAbsoluteExpiration(TimeSpan.FromDays(3));
cache.Set(cacheKey, collections, cacheEntryOptions);
}
return Results.Json(collections);
});
app.MapGet("/api/v1/collection/{index}", (int index) =>
{
return Results.Json(Osudb.Instance.GetCollection(index));
});
app.MapGet("/api/v1/audio/{*fileName}", async (string fileName, HttpContext context) =>
{
var decodedFileName = Uri.UnescapeDataString(fileName);
var filePath = Path.Combine(Osudb.osufolder, "Songs", decodedFileName);
if (!File.Exists(filePath))
{
return Results.NotFound();
}
var fileExtension = Path.GetExtension(filePath).ToLowerInvariant();
var contentType = fileExtension switch
{
".mp3" => "audio/mpeg",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
_ => "application/octet-stream",
};
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return Results.Stream(fileStream, contentType, enableRangeProcessing: true);
});
app.MapGet("/api/v1/search/active", async (string? q) =>
{
return Results.Ok(SqliteDB.activeSearch(q));
});
app.MapGet("/api/v1/search/artist", async (string? q, int? limit, int? offset) =>
{
var limitv = limit ?? 100;
var offsetv = offset ?? 0;
return Results.Ok(SqliteDB.GetArtistSearch(q, limitv, offsetv));
});
app.MapGet("/api/v1/search/songs", async (string? q, int? limit, int? offset) =>
{
var limitv = limit ?? 100;
var offsetv = offset ?? 0;
return Results.Ok();
});
app.MapGet("/api/v1/search/collections", async (string? q, int? limit, int? offset) =>
{
var limitv = limit ?? 100;
var offsetv = offset ?? 0;
return Results.Ok();
});
app.MapGet("/api/v1/images/{*filename}", async (string filename, int? h, int? w) =>
{
var decodedFileName = Uri.UnescapeDataString(filename);
var filePath = Path.Combine(Osudb.osufolder, "Songs", decodedFileName);
if (!File.Exists(filePath))
{
filePath = "default-bg.png";
}
var fileExtension = Path.GetExtension(filePath).ToLowerInvariant();
var contentType = fileExtension switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".bmp" => "image/bmp",
".webp" => "image/webp",
_ => "application/octet-stream",
};
if (w == null || h == null)
{
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
return Results.Stream(fileStream, contentType, filename);
}
using var originalImage = new Bitmap(filePath);
// If resizing is requested, resize the image
Bitmap resizedImage;
if (w.HasValue || h.HasValue)
{
resizedImage = ResizeImage(originalImage, w, h);
}
else
{
resizedImage = new Bitmap(originalImage); // Keep original size
}
// Convert the resized image to a memory stream
var memoryStream = new MemoryStream();
resizedImage.Save(memoryStream, GetImageFormat(fileExtension));
memoryStream.Position = 0; // Reset stream position
return Results.File(memoryStream, contentType);
});
static Bitmap ResizeImage(Image originalImage, int? width, int? height)
{
int newWidth = width ?? originalImage.Width;
int newHeight = height ?? originalImage.Height;
if (width == null)
{
newWidth = originalImage.Width * newHeight / originalImage.Height;
}
else if (height == null)
{
newHeight = originalImage.Height * newWidth / originalImage.Width;
}
var resizedImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(resizedImage))
{
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.DrawImage(originalImage, 0, 0, newWidth, newHeight);
}
return resizedImage;
}
static ImageFormat GetImageFormat(string extension)
{
return extension switch
{
".jpg" or ".jpeg" => ImageFormat.Jpeg,
".png" => ImageFormat.Png,
".gif" => ImageFormat.Gif,
".bmp" => ImageFormat.Bmp,
".webp" => ImageFormat.Webp,
_ => ImageFormat.Png,
};
}
Osudb.Instance.ToString();
startCloudflared();
Task.Run(() =>
{
Thread.Sleep(500);
if (!apiClient.LoadCookies())
{
Console.WriteLine("Please visit this link and paste the Value back into here: ");
var cookie = Console.ReadLine();
apiClient.SaveCookies(cookie);
}
Console.WriteLine("Ur Osu songs should now be available, please delete the cookies.json if it doesnt show up and try again.");
});
await apiClient.InitializeAsync();
app.Run();
async Task startCloudflared() {
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cloudflared",
Arguments = "tunnel --url http://localhost:5153",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.ErrorDataReceived += (sender, e) => {
if (!string.IsNullOrEmpty(e.Data))
{
ParseForUrls(e.Data);
}
};
process.Start();
process.BeginErrorReadLine();
await Task.Run(() => process.WaitForExit());
}
void ParseForUrls(string data)
{
var urlRegex = new Regex(@"https?://[^\s]*\.trycloudflare\.com");
var matches = urlRegex.Matches(data);
foreach (Match match in matches)
{
Console.WriteLine($"Login here if not done already: {match.Value}/login");
apiClient.UpdateSettingsAsync(match.Value);
}
}

View File

@@ -1,21 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:45205",
"sslPort": 44305
}
},
"profiles": {
"shitweb": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5153",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,384 +0,0 @@
using OsuParsers.Beatmaps;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SQLite;
using System.Reflection.PortableExecutable;
namespace shitweb
{
public class SqliteDB
{
private static SqliteDB instance;
private const string filename = "Beatmaps.db";
private const string dburl = $"Data Source={filename};Version=3;";
static SqliteDB() { }
public static SqliteDB Instance()
{
if (instance == null)
{
instance = new SqliteDB();
}
return instance;
}
public void setup(OsuParsers.Database.OsuDatabase osuDatabase)
{
int count = 0;
using (var connection = new SQLiteConnection(dburl))
{
connection.Open();
string createBeatmaps = @"
CREATE TABLE IF NOT EXISTS Beatmap (
BeatmapId INTEGER DEFAULT 0,
Artist TEXT DEFAULT '?????',
ArtistUnicode TEXT DEFAULT '?????',
Title TEXT DEFAULT '???????',
TitleUnicode TEXT DEFAULT '???????',
Creator TEXT DEFAULT '?????',
Difficulty TEXT DEFAULT '1',
AudioFileName TEXT DEFAULT 'unknown.mp3',
MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
FileName TEXT DEFAULT 'unknown.osu',
RankedStatus TEXT DEFAULT Unknown,
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
TotalTime INTEGER DEFAULT 0,
AudioPreviewTime INTEGER DEFAULT 0,
BeatmapSetId INTEGER DEFAULT -1,
Source TEXT DEFAULT '',
Tags TEXT DEFAULT '',
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
FolderName TEXT DEFAULT 'Unknown Folder',
UNIQUE (Artist, Title, MD5Hash)
);";
using (var command = new SQLiteCommand(createBeatmaps, connection))
{
command.ExecuteNonQuery();
}
string activeSearch = @"CREATE VIRTUAL TABLE IF NOT EXISTS BeatmapFTS USING fts4(
Title,
Artist,
);";
using (var command = new SQLiteCommand(activeSearch, connection))
{
command.ExecuteNonQuery();
}
string triggerSearchupdate = @"CREATE TRIGGER IF NOT EXISTS Beatmap_Insert_Trigger
AFTER INSERT ON Beatmap
BEGIN
INSERT INTO BeatmapFTS (Title, Artist)
VALUES (NEW.Title, NEW.Artist);
END;";
using (var command = new SQLiteCommand(triggerSearchupdate, connection))
{
command.ExecuteNonQuery();
}
string query = @"SELECT COUNT(rowid) as count FROM Beatmap";
using (var command = new SQLiteCommand(query, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
count = reader.GetInt32(reader.GetOrdinal("count"));
}
}
}
}
if (count < osuDatabase.BeatmapCount)
{
DateTime? LastMapInsert = null;
using (var connection = new SQLiteConnection(dburl))
{
connection.Open();
string query = @"SELECT MAX(LastModifiedTime) as Time FROM Beatmap"; ;
using (var command = new SQLiteCommand(query, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
try
{
LastMapInsert = reader.GetDateTime(reader.GetOrdinal("Time"));
}
catch (Exception e)
{
LastMapInsert = null;
}
}
}
}
int i = 0;
int size = osuDatabase.BeatmapCount;
Console.CursorVisible = false;
foreach (var item in osuDatabase.Beatmaps)
{
if (LastMapInsert == null || item.LastModifiedTime > LastMapInsert)
{
insertBeatmap(item);
i++;
Console.CursorTop -= 1;
Console.Write($"Inserted {i}/{size}");
}
}
}
}
}
public static List<Song> GetByRecent(int limit, int offset)
{
var songs = new List<Song>();
using (var connection = new SQLiteConnection(dburl))
{
connection.Open();
string query = @"
SELECT
MD5Hash,
Title,
Artist,
TotalTime,
Creator,
FileName,
FolderName,
AudioFileName
FROM
Beatmap
GROUP BY
BeatmapSetId
ORDER BY
LastModifiedTime DESC
LIMIT @Limit
OFFSET @Offset
";
using (var command = new SQLiteCommand(query, connection))
{
command.Parameters.AddWithValue("@Limit", limit);
command.Parameters.AddWithValue("@Offset", offset);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
songs.Add(new Song(reader));
}
}
}
}
return songs;
}
public void insertBeatmap(OsuParsers.Database.Objects.DbBeatmap beatmap)
{
using (var connection = new SQLiteConnection(dburl))
{
connection.Open();
string insertBeatmap = @"
INSERT INTO Beatmap (
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, Difficulty,
AudioFileName, MD5Hash, FileName, RankedStatus, LastModifiedTime, TotalTime,
AudioPreviewTime, BeatmapSetId, Source, Tags, LastPlayed, FolderName
) VALUES (
@BeatmapId, @Artist, @ArtistUnicode, @Title, @TitleUnicode, @Creator, @Difficulty,
@AudioFileName, @MD5Hash, @FileName, @RankedStatus, @LastModifiedTime, @TotalTime,
@AudioPreviewTime, @BeatmapSetId, @Source, @Tags, @LastPlayed, @FolderName
);";
using (var command = new SQLiteCommand(insertBeatmap, connection))
{
command.Parameters.AddWithValue("@BeatmapSetId", beatmap.BeatmapSetId);
command.Parameters.AddWithValue("@BeatmapId", beatmap.BeatmapId);
command.Parameters.AddWithValue("@Artist", beatmap.Artist);
command.Parameters.AddWithValue("@ArtistUnicode", beatmap.ArtistUnicode);
command.Parameters.AddWithValue("@Title", beatmap.Title);
command.Parameters.AddWithValue("@TitleUnicode", beatmap.TitleUnicode);
command.Parameters.AddWithValue("@Creator", beatmap.Creator);
command.Parameters.AddWithValue("@Difficulty", beatmap.Difficulty);
command.Parameters.AddWithValue("@AudioFileName", beatmap.AudioFileName);
command.Parameters.AddWithValue("@MD5Hash", beatmap.MD5Hash);
command.Parameters.AddWithValue("@FileName", beatmap.FileName);
command.Parameters.AddWithValue("@RankedStatus", beatmap.RankedStatus);
command.Parameters.AddWithValue("@LastModifiedTime", beatmap.LastModifiedTime);
command.Parameters.AddWithValue("@TotalTime", beatmap.TotalTime);
command.Parameters.AddWithValue("@AudioPreviewTime", beatmap.AudioPreviewTime);
command.Parameters.AddWithValue("@Source", beatmap.Source);
command.Parameters.AddWithValue("@Tags", beatmap.Tags);
command.Parameters.AddWithValue("@LastPlayed", beatmap.LastPlayed);
command.Parameters.AddWithValue("@FolderName", beatmap.FolderName);
int rows = command.ExecuteNonQuery();
Console.WriteLine(rows);
}
}
}
public static Song GetSongByHash(string hash)
{
using (var connection = new SQLiteConnection(dburl))
{
connection.Open();
string query = @"
SELECT
MD5Hash,
Title,
Artist,
TotalTime,
Creator,
FileName,
FolderName,
AudioFileName
FROM Beatmap
WHERE MD5Hash = @Hash;
";
using (var command = new SQLiteCommand(query, connection))
{
command.Parameters.AddWithValue("@Hash", hash);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
return new Song(reader);
}
}
}
}
return null;
}
public static ActiveSearch activeSearch(string query) {
ActiveSearch search = new ActiveSearch();
using (var connection = new SQLiteConnection(dburl))
{
string q = @"SELECT
MD5Hash,
Title,
Artist,
TotalTime,
Creator,
FileName,
FolderName,
AudioFileName
FROM Beatmap
WHERE Title LIKE @query
OR Artist LIKE @query
OR Tags LIKE @query
Group By Title
LIMIT 15";
connection.Open();
using (var command = new SQLiteCommand(q, connection))
{
command.Parameters.AddWithValue("@query", "%" + query + "%");
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
search.Songs.Add(new Song(reader));
}
}
}
string q2 = @"SELECT
Artist
FROM Beatmap
WHERE Artist LIKE @query
OR Title LIKE @query
Group By Artist
LIMIT 5";
using (var command = new SQLiteCommand(q2, connection))
{
command.Parameters.AddWithValue("@query", query + "%");
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
search.Artist.Add(reader.GetString(reader.GetOrdinal("Artist")));
}
}
}
}
return search;
}
public static List<Song> GetArtistSearch(string query, int limit, int offset) {
List<Song> songs = new List<Song>();
query = $"%{query}%";
using (var connection = new SQLiteConnection(dburl))
{
string q = @"SELECT
MD5Hash,
Title,
Artist,
TotalTime,
Creator,
FileName,
FolderName,
AudioFileName
FROM Beatmap
WHERE Artist LIKE @query
Group By Title
LIMIT @Limit
OFFSET @Offset";
connection.Open();
using (var command = new SQLiteCommand(q, connection))
{
command.Parameters.AddWithValue("@query", query);
command.Parameters.AddWithValue("@Limit", limit);
command.Parameters.AddWithValue("@Offset", offset);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
songs.Add(new Song(reader));
}
}
}
}
return songs;
}
}
}

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,4 +0,0 @@
{
"Name": "cookie_name",
"Value": "random cookie value here"
}

View File

@@ -1,158 +0,0 @@
using Microsoft.Win32;
using OsuParsers.Database;
using shitweb;
using System.Text.RegularExpressions;
public class Osudb
{
private static Osudb instance = null;
private static readonly object padlock = new object();
public static string osufolder { get; private set; }
public static CollectionDatabase CollectionDb { get; private set; }
static Osudb()
{
var key = Registry.GetValue(
@"HKEY_LOCAL_MACHINE\SOFTWARE\Classes\osu\shell\open\command",
"",
null
);
if (key != null)
{
string[] keyparts = key.ToString().Split('"');
osufolder = Path.GetDirectoryName(keyparts[1]);
Parse(osufolder);
}
else throw new Exception("Osu not Installed... ");
}
static void Parse(string filepath)
{
OsuDatabase osuDatabase = null;
string file = "/osu!.db";
if (File.Exists(filepath + file))
{
using (FileStream fileStream = new FileStream(filepath + file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 4096, useAsync: true))
{
Console.WriteLine($"Parsing {file}");
osuDatabase = OsuParsers.Decoders.DatabaseDecoder.DecodeOsu($"{filepath}{file}");
Console.WriteLine($"Parsed {file}");
fileStream.Close();
}
}
file = "/collection.db";
if (File.Exists(filepath + file))
{
using (FileStream fileStream = new FileStream(filepath + file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 4096, useAsync: true))
{
Console.WriteLine($"Parsing {file}");
CollectionDb = OsuParsers.Decoders.DatabaseDecoder.DecodeCollection($"{filepath}{file}");
Console.WriteLine($"Parsed {file}");
}
}
SqliteDB.Instance().setup(osuDatabase);
osuDatabase = null;
GC.Collect();
}
public static Osudb Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Osudb();
}
return instance;
}
}
}
public static OsuParsers.Database.Objects.Collection GetCollectionbyName(string name) {
return CollectionDb.Collections.FirstOrDefault(collection => collection.Name == name);
}
public static OsuParsers.Database.Objects.Collection GetCollectionbyIndex(int index)
{
return CollectionDb.Collections.ElementAtOrDefault(index);
}
public Collection GetCollection(int index) {
var collection = GetCollectionbyIndex(index);
if (collection == null) { return null; }
List<Song> songs = new List<Song>();
var activeId = "";
collection.MD5Hashes.ForEach(hash =>
{
var beatmap = SqliteDB.GetSongByHash(hash);
if (beatmap == null) { return; }
songs.Add(beatmap);
});
return new Collection(collection.Name, songs.Count, songs);
}
public List<CollectionPreview> GetCollections(int limit, int offset)
{
List<CollectionPreview> collections = new List<CollectionPreview>();
for (int i = offset; i < CollectionDb.Collections.Count - 1 && i < offset + limit; i++) {
var collection = CollectionDb.Collections[i];
var beatmap = SqliteDB.GetSongByHash(collection.MD5Hashes.FirstOrDefault());
collections.Add(new CollectionPreview(index: i, name: collection.Name, previewimage: beatmap.previewimage, length: collection.Count));
};
return collections;
}
public static string getBG(string songfolder, string diff)
{
string folderpath = Path.Combine(songfolder, diff);
string filepath = Path.Combine(osufolder, "Songs", folderpath);
if (File.Exists(filepath))
{
string fileContents = File.ReadAllText($@"{filepath}"); // Read the contents of the file
string pattern = @"\d+,\d+,""(?<image_filename>[^""]+\.[a-zA-Z]+)"",\d+,\d+";
Match match = Regex.Match(fileContents, pattern);
if (match.Success)
{
string background = Uri.EscapeDataString(match.Groups["image_filename"].Value);
return Path.Combine(Uri.EscapeDataString(songfolder), background);
}
pattern = @"\d+,\d+,""(?<image_filename>[^""]+\.[a-zA-Z]+)""";
match = Regex.Match(fileContents, pattern);
if (match.Success)
{
string background = Uri.EscapeDataString(match.Groups["image_filename"].Value);
return Path.Combine(Uri.EscapeDataString(songfolder), background);
}
}
return "default-bg.png";
}
}

View File

@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OsuParsers" Version="1.7.1" />
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
</ItemGroup>
<ItemGroup>
<None Update="default-bg.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34322.80
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shitweb", "shitweb.csproj", "{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A5EA63AB-B1DD-4171-922C-D01C758AD544}
EndGlobalSection
EndGlobal

View File

@@ -1,102 +0,0 @@
using OsuParsers.Database.Objects;
using OsuParsers.Enums.Database;
using OsuParsers.Enums;
using System.Data.SQLite;
using System.CodeDom;
public class Song{
public string hash {get; set;}
public string name {get; set;}
public string artist {get; set;}
public int length {get; set;}
public string url { get; set; }
public string previewimage {get; set;}
public string mapper {get; set;}
public Song(string hash, string name, string artist, int length, string url, string previewimage, string mapper) {
this.hash = hash; this.name = name; this.artist = artist; this.length = length; this.url = url; this.previewimage = previewimage; this.mapper = mapper;
}
public Song(SQLiteDataReader reader) {
string folder = reader.GetString(reader.GetOrdinal("FolderName"));
string file = reader.GetString(reader.GetOrdinal("FileName"));
string audio = reader.GetString(reader.GetOrdinal("AudioFileName"));
this.hash = reader.GetString(reader.GetOrdinal("MD5Hash"));
this.name = reader.GetString(reader.GetOrdinal("Title"));
this.artist = reader.GetString(reader.GetOrdinal("Artist"));
this.length = reader.GetInt32(reader.GetOrdinal("TotalTime"));
this.url = Uri.EscapeDataString($"{folder}/{audio}");
this.previewimage = Osudb.getBG(folder, file);
this.mapper = reader.GetString(reader.GetOrdinal("Creator"));
}
}
public class CollectionPreview{
public int index { get; set;}
public string? name {get; set;}
public int length {get; set;}
public string? previewimage {get; set;}
private CollectionPreview() { }
public CollectionPreview(int index, string name, string previewimage, int length) {
this.index = index; this.name = name; this.previewimage = previewimage; this.length = length;
}
}
public class Collection{
public string? name {get; set;}
public int length {get; set;}
public List<Song> songs { get; set;} = new List<Song>();
private Collection() { }
public Collection(string name, int length, List<Song> songs) {
this.name = name; this.length = length; this.songs = songs;
}
}
public class ActiveSearch{
public List<string> Artist { get; set; } = new List<string>();
public List<Song> Songs { get; set; } = new List<Song>();
}
public class Beatmap
{
public string? Artist { get; set; }
public string? ArtistUnicode { get; set; }
public string? Title { get; set; }
public string? TitleUnicode { get; set; }
public string? Creator { get; set; }
public string? Difficulty { get; set; }
public string? AudioFileName { get; set; }
public string? MD5Hash { get; set; }
public string? FileName { get; set; }
public RankedStatus RankedStatus { get; set; }
public DateTime LastModifiedTime { get; set; }
public int TotalTime { get; set; }
public int AudioPreviewTime { get; set; }
public int BeatmapId { get; set; }
public int BeatmapSetId { get; set; }
public string? Source { get; set; }
public string? Tags { get; set; }
public DateTime LastPlayed { get; set; }
public string? FolderName { get; set; }
}
public class BeatmapSet {
public int BeatmapSetId { get; set; }
public string? FolderName { get; set; }
public string? Creator { get; set; }
public DateTime LastModifiedTime { get; set; }
public List<Beatmap> Beatmaps { get; private set; } = new List<Beatmap>();
public void AddBeatmap(Beatmap beatmap)
{
beatmap.BeatmapSetId = this.BeatmapSetId;
Beatmaps.Add(beatmap);
}
}

View File

@@ -1,3 +0,0 @@
data/*
.vscode/
.env

View File

@@ -1,599 +0,0 @@
package main
import (
"database/sql"
"errors"
"fmt"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/juli0n21/go-osu-parser/parser"
_ "modernc.org/sqlite"
)
var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching")
var osuDB *parser.OsuDB
var osuRoot string
func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql.DB, error) {
osuDB = osuDb
osuRoot = osuroot
dir := filepath.Dir(connectionString)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755)
if err != nil {
return nil, fmt.Errorf("failed to create directory %s: %v", dir, err)
}
}
db, err := sql.Open("sqlite", connectionString)
if err != nil {
return nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err)
}
_, err = db.Exec("PRAGMA temp_store = MEMORY;")
if err != nil {
log.Fatal(err)
}
if err = createDB(db); err != nil {
return nil, err
}
if err = checkhealth(db, osuDB); err != nil {
if err = rebuildBeatmapDb(db, osuDB); err != nil {
return nil, err
}
}
if err = createCollectionDB(db); err != nil {
return nil, err
}
collectionDB, err := parser.ParseCollectionsDB(path.Join(osuRoot, "collection.db"))
if err != nil {
return nil, err
}
if err = checkCollectionHealth(db, collectionDB); err != nil {
if err = rebuildCollectionDb(db, collectionDB); err != nil {
return nil, err
}
}
return db, nil
}
func createDB(db *sql.DB) error {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS Beatmap (
BeatmapId INTEGER DEFAULT 0,
Artist TEXT DEFAULT '?????',
ArtistUnicode TEXT DEFAULT '?????',
Title TEXT DEFAULT '???????',
TitleUnicode TEXT DEFAULT '???????',
Creator TEXT DEFAULT '?????',
Difficulty TEXT DEFAULT '1',
Audio TEXT DEFAULT 'unknown.mp3',
MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
File TEXT DEFAULT 'unknown.osu',
RankedStatus TEXT DEFAULT Unknown,
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
TotalTime INTEGER DEFAULT 0,
AudioPreviewTime INTEGER DEFAULT 0,
BeatmapSetId INTEGER DEFAULT -1,
Source TEXT DEFAULT '',
Tags TEXT DEFAULT '',
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
Folder TEXT DEFAULT 'Unknown Folder',
UNIQUE (Artist, Title, MD5Hash, Difficulty)
);
`)
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_md5hash ON Beatmap(MD5Hash);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_lastModifiedTime ON Beatmap(LastModifiedTime);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_title_artist ON Beatmap(Title, Artist);")
if err != nil {
return err
}
return nil
}
func createCollectionDB(db *sql.DB) error {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS Collection (
Name TEXT DEFAULT '',
MD5Hash TEXT DEFAULT '00000000000000000000000000000000'
);
`)
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_collection_name ON Collection(Name);")
if err != nil {
return err
}
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_collection_md5hash ON Collection(MD5Hash);")
if err != nil {
return err
}
return nil
}
func checkhealth(db *sql.DB, osuDb *parser.OsuDB) error {
rows, err := db.Query(`SELECT COUNT(*) FROM Beatmap GROUP BY BeatmapSetId;`)
if err != nil {
return err
}
defer rows.Close()
var count int
if err = rows.Scan(&count); err != nil {
return err
}
if count != int(osuDb.FolderCount) {
log.Println("Folder count missmatch rebuilding db...")
return ErrBeatmapCountNotMatch
}
rows, err = db.Query(`SELECT COUNT(*) FROM Beatmap;`)
if err != nil {
return err
}
if err = rows.Scan(&count); err != nil {
return err
}
if count != int(osuDb.NumberOfBeatmaps) {
log.Println("Beatmap count missmatch rebuilding db...")
return ErrBeatmapCountNotMatch
}
return nil
}
func rebuildBeatmapDb(db *sql.DB, osuDb *parser.OsuDB) error {
if _, err := db.Exec("DROP TABLE Beatmap"); err != nil {
return err
}
if err := createDB(db); err != nil {
return err
}
stmt, err := db.Prepare(`
INSERT INTO Beatmap (
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator,
Difficulty, Audio, MD5Hash, File, RankedStatus,
LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId,
Source, Tags, LastPlayed, Folder
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
// ON CONFLICT (Artist, Title, MD5Hash) DO NOTHING
if err != nil {
return err
}
defer stmt.Close()
tx, err := db.Begin()
if err != nil {
return err
}
stmt = tx.Stmt(stmt)
for i, beatmap := range osuDb.Beatmaps {
//fmt.Println(i, beatmap.Artist, beatmap.SongTitle, beatmap.MD5Hash)
_, err := stmt.Exec(
beatmap.DifficultyID, beatmap.Artist, beatmap.ArtistUnicode,
beatmap.SongTitle, beatmap.SongTitleUnicode, beatmap.Creator,
beatmap.Difficulty, beatmap.AudioFileName, beatmap.MD5Hash,
beatmap.FileName, beatmap.RankedStatus, beatmap.LastModificationTime,
beatmap.TotalTime, beatmap.AudioPreviewStartTime, beatmap.BeatmapID,
beatmap.SongSource, beatmap.SongTags, beatmap.LastPlayed, beatmap.FolderName,
)
if err != nil {
fmt.Println(i, "hash: ", beatmap.MD5Hash, "artist:", beatmap.Artist, "title:", beatmap.SongTitle, err)
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func checkCollectionHealth(db *sql.DB, collectionDB *parser.Collections) error {
rows, err := db.Query(`SELECT COUNT(*) FROM Collection GROUP BY Name;`)
if err != nil {
return err
}
defer rows.Close()
var count int
if err = rows.Scan(&count); err != nil {
return err
}
if count != int(collectionDB.NumberOfCollections) {
return errors.New("Collection Count Not Matching")
}
rows, err = db.Query(`SELECT COUNT(*) FROM Collection;`)
if err != nil {
return err
}
if err = rows.Scan(&count); err != nil {
return err
}
sum := 0
for _, col := range collectionDB.Collections {
sum += len(col.Beatmaps)
}
if count != int(sum) {
return errors.New("Beatmap count missmatch rebuilding collections")
}
return nil
}
func rebuildCollectionDb(db *sql.DB, collectionDb *parser.Collections) error {
if _, err := db.Exec("DROP TABLE Collection"); err != nil {
return err
}
if err := createCollectionDB(db); err != nil {
return err
}
stmt, err := db.Prepare(`
INSERT INTO Collection (
Name,
MD5Hash
) VALUES (?, ?)
`)
if err != nil {
return err
}
defer stmt.Close()
tx, err := db.Begin()
if err != nil {
return err
}
stmt = tx.Stmt(stmt)
for _, col := range collectionDb.Collections {
for _, hash := range col.Beatmaps {
_, err := stmt.Exec(col.Name, hash)
if err != nil {
fmt.Println(err)
}
}
}
return tx.Commit()
}
func getBeatmapCount(db *sql.DB) int {
rows, err := db.Query("SELECT COUNT(*) FROM Beatmap")
if err != nil {
log.Println(err)
return 0
}
defer rows.Close()
var count int
if rows.Next() {
err = rows.Scan(&count)
if err != nil {
log.Println(err)
return 0
}
} else {
return 0
}
return count
}
func getRecent(db *sql.DB, limit, offset int) ([]Song, error) {
rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?", limit, offset)
if err != nil {
return []Song{}, err
}
defer rows.Close()
return scanSongs(rows)
}
func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) {
rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
if err != nil {
return ActiveSearch{}, err
}
defer rows.Close()
_, err = scanSongs(rows)
if err != nil {
return ActiveSearch{}, err
}
return ActiveSearch{}, nil
}
func getArtists(db *sql.DB, q string, limit, offset int) ([]Artist, error) {
rows, err := db.Query("SELECT Artist, COUNT(Artist) FROM Beatmap WHERE Artist LIKE ? OR Title LIKE ? GROUP BY Artist LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
if err != nil {
return []Artist{}, err
}
defer rows.Close()
artist := []Artist{}
for rows.Next() {
var a string
var c int
err := rows.Scan(&a, &c)
if err != nil {
return []Artist{}, err
}
artist = append(artist, Artist{Artist: a, Count: c})
}
return artist, nil
}
func getFavorites(db *sql.DB, q string, limit, offset int) ([]Song, error) {
rows, err := db.Query("SELECT * FROM Songs WHERE IsFavorite = 1 LIMIT ? OFFSET ?", limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
return scanSongs(rows)
}
func getCollection(db *sql.DB, limit, offset, index int) (Collection, error) {
rows, err := db.Query(`
WITH cols AS (
SELECT
c.Name,
ROW_NUMBER() OVER (ORDER BY c.Name) AS RowNumber
FROM Collection c
GROUP BY c.Name
)
SELECT
c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist,
b.Creator, b.Folder, b.File, b.Audio, b.TotalTime
FROM Collection c
Join Beatmap b ON c.MD5Hash = b.MD5Hash
WHERE c.Name = (SELECT Name FROM cols WHERE RowNumber = ?)
LIMIT ?
OFFSET ?;`, index, limit, offset)
if err != nil {
return Collection{}, err
}
defer rows.Close()
var c Collection
for rows.Next() {
s := Song{}
if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
return Collection{}, err
}
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
c.Songs = append(c.Songs, s)
}
row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name)
var count string
row.Scan(&count)
if i, err := strconv.Atoi(count); err == nil {
c.Items = i
}
return c, nil
}
func getCollectionByName(db *sql.DB, limit, offset int, name string) (Collection, error) {
rows, err := db.Query(`
SELECT
c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist,
b.Creator, b.Folder, b.File, b.Audio, b.TotalTime
FROM Collection c
Join Beatmap b ON c.MD5Hash = b.MD5Hash
WHERE c.Name = ?
LIMIT ?
OFFSET ?;`, name, limit, offset)
if err != nil {
return Collection{}, err
}
defer rows.Close()
var c Collection
for rows.Next() {
s := Song{}
if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
return Collection{}, err
}
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
c.Songs = append(c.Songs, s)
}
row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name)
var count string
row.Scan(&count)
if i, err := strconv.Atoi(count); err == nil {
c.Items = i
} else {
return Collection{}, err
}
return c, nil
}
func getCollections(db *sql.DB, q string, limit, offset int) ([]CollectionPreview, error) {
rows, err := db.Query(`
SELECT
c.Name, COUNT(b.MD5Hash), b.Folder, b.File
FROM Collection c
Join Beatmap b ON c.MD5Hash = b.MD5Hash
WHERE c.Name LIKE ?
GROUP BY c.NAME
LIMIT ?
OFFSET ?;`, "%"+q+"%", limit, offset)
if err != nil {
return []CollectionPreview{}, err
}
defer rows.Close()
var collections []CollectionPreview
for rows.Next() {
var c CollectionPreview
var folder, file, count string
if err := rows.Scan(&c.Name, &count, &folder, &file); err != nil {
return []CollectionPreview{}, err
}
if i, err := strconv.Atoi(count); err == nil {
c.Items = i
}
c.Image = extractImageFromFile(osuRoot, folder, file)
collections = append(collections, c)
}
return collections, nil
}
func getSong(db *sql.DB, hash string) (Song, error) {
row := db.QueryRow("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash = ?", hash)
s, err := scanSong(row)
return s, err
}
func scanSongs(rows *sql.Rows) ([]Song, error) {
songs := []Song{}
for rows.Next() {
var s Song
if err := rows.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
return []Song{}, err
}
bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", osuRoot, s.Folder, s.File))
if err != nil {
fmt.Println(err)
s.Image = fmt.Sprintf("404.png")
} else {
if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 {
s.Image = fmt.Sprintf("%s/%s", s.Folder, strings.Trim(bm.Events[0].EventParams[0], "\""))
}
}
songs = append(songs, s)
}
return songs, nil
}
func scanSong(row *sql.Row) (Song, error) {
s := Song{}
if err := row.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
return Song{}, err
}
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
return s, nil
}
func extractImageFromFile(osuRoot, folder, file string) string {
bm, err := parser.ParseOsuFile(filepath.Join(osuRoot, "Songs", folder, file))
if err != nil {
fmt.Println(err)
return "404.png"
}
if bm.Version > 3 {
if len(bm.Events) > 0 && len(bm.Events[0].EventParams) > 0 {
return fmt.Sprintf(`%s/%s`, folder, strings.Trim(bm.Events[0].EventParams[0], "\""))
}
} else {
fmt.Println(bm.Events)
}
return "404.png"
}
func scanCollections(rows *sql.Rows) ([]Collection, error) {
var collection []Collection
for rows.Next() {
var c Collection
if err := rows.Scan(&c); err != nil {
return []Collection{}, err
}
collection = append(collection, c)
}
return collection, nil
}
func scanCollectionPreviews(rows *sql.Rows) ([]CollectionPreview, error) {
var collection []CollectionPreview
for rows.Next() {
var c CollectionPreview
if err := rows.Scan(&c); err != nil {
return []CollectionPreview{}, err
}
collection = append(collection, c)
}
return collection, nil
}
func scanCollectionPreview(row *sql.Row) (CollectionPreview, error) {
var c CollectionPreview
if err := row.Scan(&c); err != nil {
return CollectionPreview{}, err
}
return c, nil
}

View File

@@ -1,685 +0,0 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/audio/{filepath}": {
"get": {
"description": "Retrieves a song file from the server based on the provided encoded filepath",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"files"
],
"summary": "Retrieves a song file by its encoded path",
"parameters": [
{
"type": "string",
"description": "Base64 encoded file path",
"name": "filepath",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The requested song file",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"404": {
"description": "File Not Found",
"schema": {
"type": "string"
}
}
}
}
},
"/collection": {
"get": {
"description": "Retrieves a collection of songs using the provided index.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a collection of songs by index",
"parameters": [
{
"type": "integer",
"description": "Index",
"name": "index",
"in": "query"
},
{
"type": "string",
"description": "Index",
"name": "name",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/image/{filepath}": {
"get": {
"description": "Retrieves an image file from the server based on the provided encoded filepath",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"files"
],
"summary": "Retrieves an image file by its encoded path",
"parameters": [
{
"type": "string",
"description": "Base64 encoded file path",
"name": "filepath",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The requested image file",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"404": {
"description": "File Not Found",
"schema": {
"type": "string"
}
}
}
}
},
"/login": {
"get": {
"description": "Redirects users to an external authentication page",
"tags": [
"auth"
],
"summary": "Redirect to login page",
"responses": {
"307": {
"description": "Temporary Redirect",
"schema": {
"type": "string"
}
}
}
}
},
"/ping": {
"get": {
"description": "Returns a pong response if the server is running",
"tags": [
"health"
],
"summary": "Check server health",
"responses": {
"200": {
"description": "pong",
"schema": {
"type": "string"
}
}
}
}
},
"/search/active": {
"get": {
"description": "Searches active records in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches active records based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "Active search result",
"schema": {
"$ref": "#/definitions/main.ActiveSearch"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/search/artist": {
"get": {
"description": "Searches for artists in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches for artists based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "List of artists",
"schema": {
"$ref": "#/definitions/main.Artist"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/search/collections": {
"get": {
"description": "Searches collections in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches collections based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "List of collections",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Collection"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/song/{hash}": {
"get": {
"description": "Retrieves a song using its unique hash identifier.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a song by its hash",
"parameters": [
{
"type": "string",
"description": "Song hash",
"name": "hash",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.Song"
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"404": {
"description": "Song not found",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/artist": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Returns all the Songs of a specific Artist",
"parameters": [
{
"type": "string",
"description": "Artist Name",
"name": "artist",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/favorites": {
"get": {
"description": "Retrieves favorite songs filtered by a query with pagination support.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a list of favorite songs based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/recents": {
"get": {
"description": "Retrieves recent songs with pagination support.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a list of recent songs",
"parameters": [
{
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"main.ActiveSearch": {
"description": "ActiveSearch holds search results for a given artist",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Ed Sheeran"
},
"songs": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
}
},
"main.Artist": {
"description": "Artist holds search results for a given artist",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Miku"
},
"count": {
"type": "integer",
"example": 21
}
}
},
"main.Collection": {
"description": "Collection holds a list of songs",
"type": "object",
"properties": {
"items": {
"type": "integer",
"example": 15
},
"name": {
"type": "string",
"example": "Best of 2023"
},
"songs": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
}
},
"main.Song": {
"description": "Song represents a song with metadata",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Ed Sheeran"
},
"audio": {
"type": "string",
"example": "audio.mp3"
},
"beatmap_id": {
"type": "integer",
"example": 123456
},
"creator": {
"type": "string",
"example": "JohnDoe"
},
"file": {
"type": "string",
"example": "beatmap.osu"
},
"folder": {
"type": "string",
"example": "osu/Songs/123456"
},
"image": {
"type": "string",
"example": "cover.jpg"
},
"md5_hash": {
"type": "string",
"example": "abcd1234efgh5678"
},
"title": {
"type": "string",
"example": "Shape of You"
},
"total_time": {
"type": "integer",
"example": 240
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "api/v1/",
Schemes: []string{},
Title: "go-osu-music-hoster",
Description: "Server Hosting ur own osu files over a simple Api",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@@ -1,661 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "Server Hosting ur own osu files over a simple Api",
"title": "go-osu-music-hoster",
"contact": {},
"version": "1.0"
},
"host": "/",
"basePath": "/api/v1/",
"paths": {
"/audio/{filepath}": {
"get": {
"description": "Retrieves a song file from the server based on the provided encoded filepath",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"files"
],
"summary": "Retrieves a song file by its encoded path",
"parameters": [
{
"type": "string",
"description": "Base64 encoded file path",
"name": "filepath",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The requested song file",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"404": {
"description": "File Not Found",
"schema": {
"type": "string"
}
}
}
}
},
"/collection": {
"get": {
"description": "Retrieves a collection of songs using the provided index.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a collection of songs by index",
"parameters": [
{
"type": "integer",
"description": "Index",
"name": "index",
"in": "query"
},
{
"type": "string",
"description": "Index",
"name": "name",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/image/{filepath}": {
"get": {
"description": "Retrieves an image file from the server based on the provided encoded filepath",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"files"
],
"summary": "Retrieves an image file by its encoded path",
"parameters": [
{
"type": "string",
"description": "Base64 encoded file path",
"name": "filepath",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "The requested image file",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"404": {
"description": "File Not Found",
"schema": {
"type": "string"
}
}
}
}
},
"/login": {
"get": {
"description": "Redirects users to an external authentication page",
"tags": [
"auth"
],
"summary": "Redirect to login page",
"responses": {
"307": {
"description": "Temporary Redirect",
"schema": {
"type": "string"
}
}
}
}
},
"/ping": {
"get": {
"description": "Returns a pong response if the server is running",
"tags": [
"health"
],
"summary": "Check server health",
"responses": {
"200": {
"description": "pong",
"schema": {
"type": "string"
}
}
}
}
},
"/search/active": {
"get": {
"description": "Searches active records in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches active records based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "Active search result",
"schema": {
"$ref": "#/definitions/main.ActiveSearch"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/search/artist": {
"get": {
"description": "Searches for artists in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches for artists based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "List of artists",
"schema": {
"$ref": "#/definitions/main.Artist"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/search/collections": {
"get": {
"description": "Searches collections in the database based on the query parameter",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"search"
],
"summary": "Searches collections based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit the number of results",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset for pagination",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "List of collections",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Collection"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/song/{hash}": {
"get": {
"description": "Retrieves a song using its unique hash identifier.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a song by its hash",
"parameters": [
{
"type": "string",
"description": "Song hash",
"name": "hash",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.Song"
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"404": {
"description": "Song not found",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/artist": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Returns all the Songs of a specific Artist",
"parameters": [
{
"type": "string",
"description": "Artist Name",
"name": "artist",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/favorites": {
"get": {
"description": "Retrieves favorite songs filtered by a query with pagination support.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a list of favorite songs based on a query",
"parameters": [
{
"type": "string",
"description": "Search query",
"name": "query",
"in": "query",
"required": true
},
{
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"400": {
"description": "Invalid parameter",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/songs/recents": {
"get": {
"description": "Retrieves recent songs with pagination support.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"songs"
],
"summary": "Get a list of recent songs",
"parameters": [
{
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
}
},
"definitions": {
"main.ActiveSearch": {
"description": "ActiveSearch holds search results for a given artist",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Ed Sheeran"
},
"songs": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
}
},
"main.Artist": {
"description": "Artist holds search results for a given artist",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Miku"
},
"count": {
"type": "integer",
"example": 21
}
}
},
"main.Collection": {
"description": "Collection holds a list of songs",
"type": "object",
"properties": {
"items": {
"type": "integer",
"example": 15
},
"name": {
"type": "string",
"example": "Best of 2023"
},
"songs": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Song"
}
}
}
},
"main.Song": {
"description": "Song represents a song with metadata",
"type": "object",
"properties": {
"artist": {
"type": "string",
"example": "Ed Sheeran"
},
"audio": {
"type": "string",
"example": "audio.mp3"
},
"beatmap_id": {
"type": "integer",
"example": 123456
},
"creator": {
"type": "string",
"example": "JohnDoe"
},
"file": {
"type": "string",
"example": "beatmap.osu"
},
"folder": {
"type": "string",
"example": "osu/Songs/123456"
},
"image": {
"type": "string",
"example": "cover.jpg"
},
"md5_hash": {
"type": "string",
"example": "abcd1234efgh5678"
},
"title": {
"type": "string",
"example": "Shape of You"
},
"total_time": {
"type": "integer",
"example": 240
}
}
}
}
}

View File

@@ -1,446 +0,0 @@
basePath: /api/v1/
definitions:
main.ActiveSearch:
description: ActiveSearch holds search results for a given artist
properties:
artist:
example: Ed Sheeran
type: string
songs:
items:
$ref: '#/definitions/main.Song'
type: array
type: object
main.Artist:
description: Artist holds search results for a given artist
properties:
artist:
example: Miku
type: string
count:
example: 21
type: integer
type: object
main.Collection:
description: Collection holds a list of songs
properties:
items:
example: 15
type: integer
name:
example: Best of 2023
type: string
songs:
items:
$ref: '#/definitions/main.Song'
type: array
type: object
main.Song:
description: Song represents a song with metadata
properties:
artist:
example: Ed Sheeran
type: string
audio:
example: audio.mp3
type: string
beatmap_id:
example: 123456
type: integer
creator:
example: JohnDoe
type: string
file:
example: beatmap.osu
type: string
folder:
example: osu/Songs/123456
type: string
image:
example: cover.jpg
type: string
md5_hash:
example: abcd1234efgh5678
type: string
title:
example: Shape of You
type: string
total_time:
example: 240
type: integer
type: object
host: /
info:
contact: {}
description: Server Hosting ur own osu files over a simple Api
title: go-osu-music-hoster
version: "1.0"
paths:
/audio/{filepath}:
get:
consumes:
- application/json
description: Retrieves a song file from the server based on the provided encoded
filepath
parameters:
- description: Base64 encoded file path
in: path
name: filepath
required: true
type: string
produces:
- application/json
responses:
"200":
description: The requested song file
schema:
type: file
"400":
description: Bad Request
schema:
type: string
"404":
description: File Not Found
schema:
type: string
summary: Retrieves a song file by its encoded path
tags:
- files
/collection:
get:
consumes:
- application/json
description: Retrieves a collection of songs using the provided index.
parameters:
- description: Index
in: query
name: index
type: integer
- description: Index
in: query
name: name
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/main.Song'
type: array
"400":
description: Invalid parameter
schema:
type: string
"500":
description: Internal server error
schema:
type: string
summary: Get a collection of songs by index
tags:
- songs
/image/{filepath}:
get:
consumes:
- application/json
description: Retrieves an image file from the server based on the provided encoded
filepath
parameters:
- description: Base64 encoded file path
in: path
name: filepath
required: true
type: string
produces:
- application/json
responses:
"200":
description: The requested image file
schema:
type: file
"400":
description: Bad Request
schema:
type: string
"404":
description: File Not Found
schema:
type: string
summary: Retrieves an image file by its encoded path
tags:
- files
/login:
get:
description: Redirects users to an external authentication page
responses:
"307":
description: Temporary Redirect
schema:
type: string
summary: Redirect to login page
tags:
- auth
/ping:
get:
description: Returns a pong response if the server is running
responses:
"200":
description: pong
schema:
type: string
summary: Check server health
tags:
- health
/search/active:
get:
consumes:
- application/json
description: Searches active records in the database based on the query parameter
parameters:
- description: Search query
in: query
name: query
required: true
type: string
- default: 10
description: Limit the number of results
in: query
name: limit
type: integer
- default: 0
description: Offset for pagination
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: Active search result
schema:
$ref: '#/definitions/main.ActiveSearch'
"400":
description: Bad Request
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
summary: Searches active records based on a query
tags:
- search
/search/artist:
get:
consumes:
- application/json
description: Searches for artists in the database based on the query parameter
parameters:
- description: Search query
in: query
name: query
required: true
type: string
- default: 10
description: Limit the number of results
in: query
name: limit
type: integer
- default: 0
description: Offset for pagination
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: List of artists
schema:
$ref: '#/definitions/main.Artist'
"400":
description: Bad Request
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
summary: Searches for artists based on a query
tags:
- search
/search/collections:
get:
consumes:
- application/json
description: Searches collections in the database based on the query parameter
parameters:
- description: Search query
in: query
name: query
required: true
type: string
- default: 10
description: Limit the number of results
in: query
name: limit
type: integer
- default: 0
description: Offset for pagination
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: List of collections
schema:
items:
$ref: '#/definitions/main.Collection'
type: array
"400":
description: Bad Request
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
summary: Searches collections based on a query
tags:
- search
/song/{hash}:
get:
consumes:
- application/json
description: Retrieves a song using its unique hash identifier.
parameters:
- description: Song hash
in: path
name: hash
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/main.Song'
"400":
description: Invalid parameter
schema:
type: string
"404":
description: Song not found
schema:
type: string
summary: Get a song by its hash
tags:
- songs
/songs/artist:
get:
consumes:
- application/json
parameters:
- description: Artist Name
in: query
name: artist
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/main.Song'
type: array
"400":
description: Bad Request
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
summary: Returns all the Songs of a specific Artist
tags:
- songs
/songs/favorites:
get:
consumes:
- application/json
description: Retrieves favorite songs filtered by a query with pagination support.
parameters:
- description: Search query
in: query
name: query
required: true
type: string
- default: 10
description: Limit
in: query
name: limit
type: integer
- default: 0
description: Offset
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/main.Song'
type: array
"400":
description: Invalid parameter
schema:
type: string
"500":
description: Internal server error
schema:
type: string
summary: Get a list of favorite songs based on a query
tags:
- songs
/songs/recents:
get:
consumes:
- application/json
description: Retrieves recent songs with pagination support.
parameters:
- default: 10
description: Limit
in: query
name: limit
type: integer
- default: 0
description: Offset
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/main.Song'
type: array
"500":
description: Internal server error
schema:
type: string
summary: Get a list of recent songs
tags:
- songs
swagger: "2.0"

View File

@@ -1,34 +0,0 @@
module backend
go 1.24.3
require (
github.com/joho/godotenv v1.5.1
github.com/juli0n21/go-osu-parser v0.0.8
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.4
modernc.org/sqlite v1.34.5
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/swaggo/files v1.0.1 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/tools v0.29.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
)

View File

@@ -1,115 +0,0 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juli0n21/go-osu-parser v0.0.8 h1:aQtuhAniGvpUw446arhq/3aUOK9YvZEkL7aYUGlViAo=
github.com/juli0n21/go-osu-parser v0.0.8/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View File

@@ -1,443 +0,0 @@
package main
import (
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"strconv"
_ "backend/docs"
"github.com/joho/godotenv"
httpSwagger "github.com/swaggo/http-swagger"
"github.com/juli0n21/go-osu-parser/parser"
)
var ErrRequiredParameterNotPresent = errors.New("required parameter missing")
var ErrFailedToParseEncoded = errors.New("invalid encoded value")
var ErrFileNotFound = errors.New("file not found")
type Server struct {
Port string
OsuDir string
Db *sql.DB
OsuDb *parser.OsuDB
Env map[string]string
}
func (s *Server) registerRoutes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/ping", s.ping)
mux.HandleFunc("/api/v1/login", s.login)
mux.HandleFunc("/api/v1/song/{hash}/", s.song)
mux.HandleFunc("/api/v1/songs/recent", s.recents)
mux.HandleFunc("/api/v1/songs/favorites", s.favorites)
mux.HandleFunc("/api/v1/songs/artist", s.aristsSongs)
mux.HandleFunc("/api/v1/collection", s.collection)
mux.HandleFunc("/api/v1/search/collections", s.collectionSearch)
mux.HandleFunc("/api/v1/search/active", s.activeSearch)
mux.HandleFunc("/api/v1/search/artist", s.artistSearch)
mux.HandleFunc("/api/v1/audio/{filepath}", s.songFile)
mux.HandleFunc("/api/v1/image/{filepath}", s.imageFile)
mux.HandleFunc("/api/v1/callback", s.callback)
mux.Handle("/swagger/", httpSwagger.WrapHandler)
return corsMiddleware(logRequests(mux))
}
func run(s *Server) {
mux := s.registerRoutes()
fmt.Println("starting server on http://localhost" + s.Port)
log.Fatal(http.ListenAndServe(s.Port, mux))
}
func logRequests(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
// ping godoc
//
// @Summary Check server health
// @Description Returns a pong response if the server is running
// @Tags health
// @Success 200 {string} string "pong"
// @Router /ping [get]
func (s *Server) ping(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong")
}
// login godoc
//
// @Summary Redirect to login page
// @Description Redirects users to an external authentication page
// @Tags auth
// @Success 307 {string} string "Temporary Redirect"
// @Router /login [get]
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
}
// song godoc
//
// @Summary Get a song by its hash
// @Description Retrieves a song using its unique hash identifier.
// @Tags songs
// @Accept json
// @Produce json
// @Param hash path string true "Song hash"
// @Success 200 {object} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 404 {string} string "Song not found"
// @Router /song/{hash} [get]
func (s *Server) song(w http.ResponseWriter, r *http.Request) {
hash := r.PathValue("hash")
if hash == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
song, err := getSong(s.Db, hash)
if err != nil {
fmt.Println(err)
http.Error(w, "beatmap not found by hash", http.StatusNotFound)
return
}
writeJSON(w, song, http.StatusOK)
}
// recents godoc
//
// @Summary Get a list of recent songs
// @Description Retrieves recent songs with pagination support.
// @Tags songs
// @Accept json
// @Produce json
// @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0)
// @Success 200 {array} Song
// @Failure 500 {string} string "Internal server error"
// @Router /songs/recents [get]
func (s *Server) recents(w http.ResponseWriter, r *http.Request) {
limit, offset := pagination(r)
recent, err := getRecent(s.Db, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// favorites godoc
//
// @Summary Get a list of favorite songs based on a query
// @Description Retrieves favorite songs filtered by a query with pagination support.
// @Tags songs
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit" default(10)
// @Param offset query int false "Offset" default(0)
// @Success 200 {array} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 500 {string} string "Internal server error"
// @Router /songs/favorites [get]
func (s *Server) favorites(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
if query == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
limit, offset := pagination(r)
favorites, err := getFavorites(s.Db, query, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, favorites, http.StatusOK)
}
// collection godoc
//
// @Summary Get a collection of songs by index
// @Description Retrieves a collection of songs using the provided index.
// @Tags songs
// @Accept json
// @Produce json
// @Param index query int false "Index"
// @Param name query string false "Index"
// @Success 200 {array} Song
// @Failure 400 {string} string "Invalid parameter"
// @Failure 500 {string} string "Internal server error"
// @Router /collection [get]
func (s *Server) collection(w http.ResponseWriter, r *http.Request) {
limit, offset := pagination(r)
name := r.URL.Query().Get("name")
if name != "" {
c, err := getCollectionByName(s.Db, limit, offset, name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, c, http.StatusOK)
return
}
index, err := strconv.Atoi(r.URL.Query().Get("index"))
if err != nil {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
} else {
c, err := getCollection(s.Db, limit, offset, index)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, c, http.StatusOK)
return
}
}
// @Summary Searches collections based on a query
// @Description Searches collections in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {array} Collection "List of collections"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/collections [get]
func (s *Server) collectionSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
limit, offset := pagination(r)
preview, err := getCollections(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, preview, http.StatusOK)
}
// @Summary Returns all the Songs of a specific Artist
// @Tags songs
// @Accept json
// @Produce json
// @Param artist query string true "Artist Name"
// @Success 200 {array} Song
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /songs/artist [get]
func (s *Server) aristsSongs(w http.ResponseWriter, r *http.Request) {
artist := r.URL.Query().Get("artist")
if artist == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
writeJSON(w, []Song{}, http.StatusOK)
}
// @Summary Searches active records based on a query
// @Description Searches active records in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {object} ActiveSearch "Active search result"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/active [get]
func (s *Server) activeSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
//TODO
limit, offset := pagination(r)
recent, err := getSearch(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, recent, http.StatusOK)
}
// @Summary Searches for artists based on a query
// @Description Searches for artists in the database based on the query parameter
// @Tags search
// @Accept json
// @Produce json
// @Param query query string true "Search query"
// @Param limit query int false "Limit the number of results" default(10)
// @Param offset query int false "Offset for pagination" default(0)
// @Success 200 {object} Artist "List of artists"
// @Failure 400 {object} string "Bad Request"
// @Failure 500 {object} string "Internal Server Error"
// @Router /search/artist [get]
func (s *Server) artistSearch(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("query")
if q == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
//TODO
limit, offset := pagination(r)
a, err := getArtists(s.Db, q, limit, offset)
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, a, http.StatusOK)
}
// @Summary Retrieves a song file by its encoded path
// @Description Retrieves a song file from the server based on the provided encoded filepath
// @Tags files
// @Accept json
// @Produce json
// @Param filepath path string true "Base64 encoded file path"
// @Success 200 {file} File "The requested song file"
// @Failure 400 {object} string "Bad Request"
// @Failure 404 {object} string "File Not Found"
// @Router /audio/{filepath} [get]
func (s *Server) songFile(w http.ResponseWriter, r *http.Request) {
f := r.PathValue("filepath")
if f == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
filename, err := base64.RawStdEncoding.DecodeString(f)
if err != nil {
fmt.Println(err)
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
file, err := os.Open(s.OsuDir + "Songs/" + string(filename))
if err != nil {
fmt.Println(err)
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
return
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
fmt.Println(err)
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
return
}
http.ServeContent(w, r, stat.Name(), stat.ModTime(), file)
}
// @Summary Retrieves an image file by its encoded path
// @Description Retrieves an image file from the server based on the provided encoded filepath
// @Tags files
// @Accept json
// @Produce json
// @Param filepath path string true "Base64 encoded file path"
// @Success 200 {file} File "The requested image file"
// @Failure 400 {object} string "Bad Request"
// @Failure 404 {object} string "File Not Found"
// @Router /image/{filepath} [get]
func (s *Server) imageFile(w http.ResponseWriter, r *http.Request) {
f := r.PathValue("filepath")
if f == "" {
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
return
}
filename, err := base64.RawStdEncoding.DecodeString(f)
if err != nil {
fmt.Println(err)
http.Error(w, ErrFailedToParseEncoded.Error(), http.StatusBadRequest)
return
}
http.ServeFile(w, r, s.OsuDir+"Songs/"+string(filename))
}
func (s *Server) callback(w http.ResponseWriter, r *http.Request) {
cookie := r.URL.Query().Get("COOKIE")
if cookie != "" {
s.Env["COOKIE"] = cookie
godotenv.Write(s.Env, ".env")
}
}
func writeJSON(w http.ResponseWriter, v any, status int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
fmt.Println(err)
http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
}
}
func pagination(r *http.Request) (int, int) {
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil || limit <= 0 || limit > 100 {
limit = 100
}
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil || offset < 0 {
offset = 0
}
return limit, offset
}

View File

@@ -1,163 +0,0 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path"
"regexp"
"strings"
"github.com/joho/godotenv"
"github.com/juli0n21/go-osu-parser/parser"
)
// @title go-osu-music-hoster
// @version 1.0
// @description Server Hosting ur own osu files over a simple Api
// @host /
// @BasePath /api/v1/
func main() {
envMap, err := godotenv.Read(".env")
if err != nil {
fmt.Println("Error reading .env file")
}
if envMap["OSU_PATH"] == "" {
fmt.Println("Osu Path not found! Please paste the full path to your osu! folder.")
fmt.Println("Osu Path not found pls paste the full Path to ur osu! folder \n it should start with 'C://' and can be opened from the Settings menu!)\n path: ")
fmt.Scanln(&osuRoot)
osuRoot = strings.TrimSpace(osuRoot)
envMap["OSU_PATH"] = osuRoot
godotenv.Write(envMap, ".env")
}
osuRoot := envMap["OSU_PATH"]
cookie := envMap["COOKIE"]
port := GetEnv(envMap["PORT"], ":8080")
filename := path.Join(osuRoot, "osu!.db")
osuDb, err := parser.ParseOsuDB(filename)
if err != nil {
log.Fatal(err)
}
if cookie == "" {
fmt.Println("No Authentication found please follow the link to log in!\n http://proxy.illegalesachen.download/login")
}
url, err := StartCloudflared(port)
if err != nil {
log.Fatalf("Cloudflared service couldnt be started: %v", err)
}
if err = sendUrl(url, cookie); err != nil {
log.Fatalf("Couldnt Update Endpoint url with Proxy: %v", err)
}
db, err := initDB("./data/music.db", osuDb, osuRoot)
if err != nil {
log.Fatal(err)
}
s := &Server{
Port: port,
Db: db,
OsuDir: osuRoot,
Env: envMap,
}
run(s)
}
func GetEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok && value != "" {
return value
}
return fallback
}
func StartCloudflared(port string) (string, error) {
cmd := exec.Command("cloudflared", "tunnel", "--url", fmt.Sprintf("http://localhost%s", port))
stderr, err := cmd.StderrPipe()
if err != nil {
return "", fmt.Errorf("Error creating StderrPipe: %v", err)
}
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("Error starting command: %v", err)
}
stderrScanner := bufio.NewScanner(stderr)
urlRegex := regexp.MustCompile(`https?://[\w.-]+\.trycloudflare\.com`)
for stderrScanner.Scan() {
line := stderrScanner.Text()
if url := urlRegex.FindString(line); url != "" {
fmt.Println("Found URL:", url)
return url, nil
}
}
if err := cmd.Wait(); err != nil {
return "", fmt.Errorf("Error waiting for command: %v", err)
}
if err := stderrScanner.Err(); err != nil {
return "", fmt.Errorf("Error reading stderr: %v", err)
}
return "", fmt.Errorf("no url found")
}
func sendUrl(endpoint, cookie string) error {
url := GetEnv("PROXY_URL", "https://proxy.illegalesachen.download/settings")
payload := struct {
Sharing *bool `json:"sharing"`
Endpoint string `json:"endpoint"`
}{
Endpoint: endpoint,
}
body, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("Error marshalling payload: %v", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("failed to create request: %v", err)
}
req.Header.Add("Content-Type", "application/json")
req.AddCookie(&http.Cookie{
Name: "session_cookie",
Value: cookie,
})
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error sending request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error in request: %s", resp.Status)
}
return nil
}

View File

@@ -1,46 +0,0 @@
package main
// Song represents a song entity
// @Description Song represents a song with metadata
type Song struct {
BeatmapID int `json:"beatmap_id" example:"123456"`
MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"`
Title string `json:"title" example:"Shape of You"`
Artist string `json:"artist" example:"Ed Sheeran"`
Creator string `json:"creator" example:"JohnDoe"`
Folder string `json:"folder" example:"osu/Songs/123456"`
File string `json:"file" example:"beatmap.osu"`
Audio string `json:"audio" example:"audio.mp3"`
TotalTime int64 `json:"total_time" example:"240"`
Image string `json:"image" example:"cover.jpg"`
}
// CollectionPreview represents a preview of a song collection
// @Description CollectionPreview contains summary data of a song collection
type CollectionPreview struct {
Name string `json:"name" example:"Collection Name"`
Image string `json:"image" example:"cover.jpg"`
Items int `json:"items" example:"10"`
}
// Collection represents a full song collection
// @Description Collection holds a list of songs
type Collection struct {
Name string `json:"name" example:"Best of 2023"`
Items int `json:"items" example:"15"`
Songs []Song `json:"songs"`
}
// ActiveSearch represents an active song search query
// @Description ActiveSearch holds search results for a given artist
type ActiveSearch struct {
Artist string `json:"artist" example:"Ed Sheeran"`
Songs []Song `json:"songs"`
}
// Artist represents an active song search query
// @Description Artist holds search results for a given artist
type Artist struct {
Artist string `json:"artist" example:"Miku"`
Count int `json:"count" example:"21"`
}