diff --git a/backend/HttpClient.cs b/backend/HttpClient.cs new file mode 100644 index 0000000..6d2f11b --- /dev/null +++ b/backend/HttpClient.cs @@ -0,0 +1,85 @@ +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(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") + { + } + } +} \ No newline at end of file diff --git a/backend/Program.cs b/backend/Program.cs index 230e6a5..fdaa089 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -1,11 +1,17 @@ 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); @@ -25,18 +31,24 @@ builder.Services.AddCors(options => var app = builder.Build(); app.UseCors("AllowAll"); +var apiClient = new ApiClient(); + + app.MapGet("/ping", () => "pong"); -// Define the API routes -app.MapGet("/api/v1/songs/{hash}", (string hash) => - { +app.MapGet("/login", () => { + return Results.Redirect("https://proxy.illegalesachen.download/login"); +}); - return Results.Ok(new { hash }); - }); +app.MapGet("/api/v1/songs/{hash}", (string hash) => +{ + + return Results.Ok(new { hash }); +}); -app.MapGet("/api/v1/songs/recent", (int? limit, int? offset) => - { +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 @@ -225,4 +237,63 @@ static ImageFormat GetImageFormat(string extension) } Osudb.Instance.ToString(); -app.Run(); \ No newline at end of file +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); + } +} diff --git a/backend/cookies.json b/backend/cookies.json new file mode 100644 index 0000000..3648a73 --- /dev/null +++ b/backend/cookies.json @@ -0,0 +1,4 @@ +{ + "Name": "session_cookie", + "Value": "session_cookie_c_PhxGlrrYcWY1h9cQ6mu_LxsDIVu0u_Nv0qAK1WmvcZiZXQ_FcwXZZXDk5Tm0qrqZTHHs800Pv5bTMGIrfa8Q==" +} \ No newline at end of file diff --git a/frontend/src/script/types.ts b/frontend/src/script/types.ts index 629dae5..baa4a9d 100644 --- a/frontend/src/script/types.ts +++ b/frontend/src/script/types.ts @@ -6,12 +6,19 @@ export type Song = { url: string; previewimage: string; mapper: string; - }; - +}; + export type CollectionPreview = { index: number; name: string; length: number; previewimage: string; }; - \ No newline at end of file + +export type Me = { + id: number; + name: string; + avatar_url: string; + endpoint: string; + share: boolean; +}; diff --git a/frontend/src/stores/userStore.ts b/frontend/src/stores/userStore.ts index ad2e3e1..7182ebf 100644 --- a/frontend/src/stores/userStore.ts +++ b/frontend/src/stores/userStore.ts @@ -1,10 +1,27 @@ import { defineStore } from 'pinia'; import { ref } from 'vue'; -import type { Song, CollectionPreview } from '@/script/types'; +import type { Song, CollectionPreview, Me } from '@/script/types'; export const useUserStore = defineStore('userStore', () => { const userId = ref(null) const baseUrl = ref('https://service.illegalesachen.download/') + const proxyUrl = ref('https://proxy.illegalesachen.download/') + + const User = ref(null) + + function saveUser(user: Me) { + localStorage.setItem('activeUser', JSON.stringify(user)); + } + + function loadUser(): Me | null { + const user = localStorage.getItem('activeUser'); + return user ? JSON.parse(user) : null; + } + + function setUser(user: Me) { + User.value = user; + saveUser(user) + } async function fetchSong(hash: string): Promise { try { @@ -30,7 +47,7 @@ export const useUserStore = defineStore('userStore', () => { } } - async function fetchWithCache(cacheKey: string, url: string, cacheDuration: number = 24 * 60 * 60 * 1000): Promise { + async function fetchWithCache(cacheKey: string, url: string, cacheDuration: number = 24 * 60 * 60 * 1): Promise { const cacheTimestampKey = `${cacheKey}_timestamp`; const cachedData = localStorage.getItem(cacheKey); @@ -96,5 +113,35 @@ export const useUserStore = defineStore('userStore', () => { return fetchWithCache(cacheKey, url); } - return { fetchSong, fetchActiveSearch, fetchSearchArtist, fetchCollections, fetchCollection, fetchRecent, fetchFavorites, userId, baseUrl } + async function fetchMe(): Promise { + const url = `${proxyUrl.value}me`; + + try { + const response = await fetch(url, { + method: 'GET', + credentials: 'include' + }); + console.log(response); + + if (response.redirected) { + window.open(response.url, '_blank'); + return { "redirected": true }; + } + + if (!response.ok) { + console.error(`Fetch failed with status: ${response.status} ${response.statusText}`); + return { id: -1 } as Me; + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Fetch error:', error); + return {} as Me; + } + } + + setUser(loadUser()); + + return { fetchSong, fetchActiveSearch, fetchSearchArtist, fetchCollections, fetchCollection, fetchRecent, fetchFavorites, fetchMe, userId, baseUrl, proxyUrl, User, setUser } }) diff --git a/frontend/src/views/MeView.vue b/frontend/src/views/MeView.vue index 2afe33e..de890ab 100644 --- a/frontend/src/views/MeView.vue +++ b/frontend/src/views/MeView.vue @@ -12,6 +12,8 @@ const actionColor = ref(''); const infoColor = ref(''); const borderColor = ref(''); +const loginStatus = ref('Login'); + function update() { var input = document.getElementById("url-input") as HTMLAudioElement; console.log(input.value) @@ -34,6 +36,24 @@ function save(bg: string | null, main: string | null, info: string | null, borde console.log("bg", bgColor.value, "action:", actionColor.value, "info", infoColor.value, "border", borderColor.value) } +async function getMe() { + + const data = await userStore.fetchMe() as Me; + if (data.redirected == true) { + loginStatus.value = "waiting for login, click to refresh!" + console.log("redirect detected"); + } + + console.log(data) + if (data.id === null || data.id === undefined || Object.keys(data).length === 0) { + return + } + + console.log("active user: ", data.name) + userStore.setUser(data); + +} + onMounted(() => { reset(); }) @@ -69,12 +89,17 @@ function reset() {

Meeeeee


-
- + +
+
-

User: {{ 'JuLi0n_' }}

-

Api: Not Connected

-

Sharing:

+

{{ userStore.User.name }}

+

{{ userStore.User.endpoint == "" ? 'Not Connected' : 'Connected' }}

+

Sharing:

+ +
diff --git a/proxy/auth.go b/proxy/auth.go index 3690c92..cc4c227 100644 --- a/proxy/auth.go +++ b/proxy/auth.go @@ -86,8 +86,7 @@ func (c *OsuApiClient) sendRequest(req *http.Request, v interface{}) error { return nil } -func LoginRedirect(w http.ResponseWriter, r *http.Request) { - +func LoginMiddlePage(w http.ResponseWriter, r *http.Request) { cookie, ok := r.Context().Value("cookie").(string) if !ok || cookie == "" { @@ -98,13 +97,32 @@ func LoginRedirect(w http.ResponseWriter, r *http.Request) { var clientid = os.Getenv("CLIENT_ID") var redirect_uri = os.Getenv("REDIRECT_URI") + "/oauth/code" - http.Redirect(w, r, - fmt.Sprintf("https://osu.ppy.sh/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s", - clientid, - redirect_uri, - strings.Join(scopes, " "), - cookie), - http.StatusTemporaryRedirect) + + html := ` + + + + + + Login Required + + +

Redirecting...

+ Click here if ur not being Redirected! + + + + ` + + loginURL := fmt.Sprintf("https://osu.ppy.sh/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s", + clientid, + redirect_uri, + strings.Join(scopes, " "), + cookie) + fmt.Fprintf(w, html, loginURL, loginURL) + return } func Oauth(w http.ResponseWriter, r *http.Request) { @@ -204,13 +222,41 @@ func Oauth(w http.ResponseWriter, r *http.Request) { user.UserID = apiuser.ID user.Name = apiuser.Username user.AvatarUrl = apiuser.AvatarURL + user.Share = false SaveCookie(user.UserID, cookie) if err = SaveUser(user); err != nil { fmt.Println(err) } - JSONResponse(w, http.StatusCreated, user) + var html = fmt.Sprintf(` + + + + + Login Success + + + + + + + + + `, cookie) + + fmt.Fprint(w, html) + return + } type AuthToken struct { diff --git a/proxy/database.db b/proxy/database.db index 104ea16..dc6a2a0 100644 Binary files a/proxy/database.db and b/proxy/database.db differ diff --git a/proxy/db.go b/proxy/db.go index 6685d2a..505404c 100644 --- a/proxy/db.go +++ b/proxy/db.go @@ -14,6 +14,7 @@ type User struct { Name string `json:"name"` AvatarUrl string `json:"avatar_url"` EndPoint string `json:"endpoint"` + Share bool `json:"share"` Token `json:"-"` } @@ -127,3 +128,9 @@ func UpdateUserEndPoint(userID int, endPoint string) error { _, err := db.Exec(query, endPoint, userID) return err } + +func UpdateUserShare(userID int, sharing bool) error { + query := "UPDATE users SET sharing = ? WHERE id = ?" + _, err := db.Exec(query, sharing, userID) + return err +} diff --git a/proxy/middleware.go b/proxy/middleware.go index adc7755..8eea01e 100644 --- a/proxy/middleware.go +++ b/proxy/middleware.go @@ -20,7 +20,7 @@ func AuthMiddleware(next http.Handler) http.Handler { user, err := GetUserByCookie(cookie.Value) if err != nil || cookie.Value == "" { - http.Error(w, "Unauthorized", http.StatusUnauthorized) + http.Redirect(w, r, "/login", http.StatusTemporaryRedirect) return } @@ -43,10 +43,13 @@ func CookieMiddleware(next http.Handler) http.Handler { HttpOnly: true, Secure: true, Path: "/", + Domain: ".illegalesachen.download", + SameSite: http.SameSiteLaxMode, } http.SetCookie(w, newCookie) cookie = newCookie + r.AddCookie(cookie) } ctx := context.WithValue(r.Context(), "cookie", cookie.Value) @@ -54,6 +57,30 @@ func CookieMiddleware(next http.Handler) http.Handler { }) } +func CORS(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Access-Control-Allow-Origin", "https://music.illegalesachen.download") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Credentials", "true") + + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + next.ServeHTTP(w, r) + }) +} + +func Logger(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + fmt.Println(r.URL) + next.ServeHTTP(w, r) + }) + +} + func generateRandomString(length int) string { bytes := make([]byte, length) _, err := rand.Read(bytes) diff --git a/proxy/routes.go b/proxy/routes.go index dbaa0dc..754a0d6 100644 --- a/proxy/routes.go +++ b/proxy/routes.go @@ -12,17 +12,29 @@ func run() error { port := os.Getenv("PORT") mux := http.NewServeMux() - mux.Handle("/me", AuthMiddleware(http.HandlerFunc(MeHandler))) - mux.Handle("/login", http.HandlerFunc(LoginRedirect)) + mux.Handle("GET /me", AuthMiddleware(http.HandlerFunc(MeHandler))) + mux.Handle("GET /login", http.HandlerFunc(LoginMiddlePage)) + mux.Handle("GET /oauth/code", http.HandlerFunc(Oauth)) + mux.Handle("POST /settings", AuthMiddleware(http.HandlerFunc(Settings))) - mux.Handle("/oauth/code", http.HandlerFunc(Oauth)) + // mux.Handle("POST /setting", ); fmt.Println("Starting Server on", port) //global middleware - handler := CookieMiddleware(mux) + handler := CORS(CookieMiddleware(Logger(mux))) - return http.ListenAndServe(port, handler) + finalMux := http.NewServeMux() + + finalMux.Handle("/", handler) + + finalMux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte("pong")) + }) + + return http.ListenAndServe(port, finalMux) } func MeHandler(w http.ResponseWriter, r *http.Request) { @@ -37,6 +49,37 @@ func MeHandler(w http.ResponseWriter, r *http.Request) { JSONResponse(w, http.StatusOK, user) } +func Settings(w http.ResponseWriter, r *http.Request) { + type settings struct { + Sharing *bool `json:"sharing"` + Endpoint string `json:"endpoint"` + } + + user, ok := r.Context().Value("user").(*User) + if !ok || user == nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + var s settings + + if err := json.NewDecoder(r.Body).Decode(&s); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + if s.Endpoint != "" { + UpdateUserEndPoint(user.UserID, s.Endpoint) + } + + if s.Sharing != nil { + UpdateUserShare(user.UserID, *s.Sharing) + } + + w.WriteHeader(http.StatusOK) + return +} + func JSONResponse(w http.ResponseWriter, statusCode int, data interface{}) { w.Header().Set("Content-Type", "application/json")