mirror of
https://github.com/markuryy/shark.git
synced 2025-12-13 12:01:01 +00:00
feat(auth): implement purple music app authentication
This commit is contained in:
303
src/routes/services/deezer/+page.svelte
Normal file
303
src/routes/services/deezer/+page.svelte
Normal file
@@ -0,0 +1,303 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { deezerAuth, loadDeezerAuth, saveArl, saveUser, clearDeezerAuth } from '$lib/stores/deezer';
|
||||
import { deezerAPI } from '$lib/services/deezer';
|
||||
|
||||
let arlInput = $state('');
|
||||
let isLoading = $state(false);
|
||||
let errorMessage = $state('');
|
||||
let successMessage = $state('');
|
||||
let testingAuth = $state(false);
|
||||
let authTestResult = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
await loadDeezerAuth();
|
||||
});
|
||||
|
||||
async function handleLogin() {
|
||||
if (!arlInput || arlInput.trim().length === 0) {
|
||||
errorMessage = 'Please enter an ARL token';
|
||||
return;
|
||||
}
|
||||
|
||||
if (arlInput.trim().length !== 192) {
|
||||
errorMessage = 'ARL token should be 192 characters long';
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
errorMessage = '';
|
||||
successMessage = '';
|
||||
|
||||
try {
|
||||
const result = await deezerAPI.loginViaArl(arlInput.trim());
|
||||
|
||||
if (result.success && result.user) {
|
||||
await saveArl(arlInput.trim());
|
||||
await saveUser(result.user);
|
||||
successMessage = `Successfully logged in as ${result.user.name}!`;
|
||||
arlInput = '';
|
||||
} else {
|
||||
errorMessage = result.error || 'Login failed. Please check your ARL token.';
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage = `Login error: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
await clearDeezerAuth();
|
||||
successMessage = 'Logged out successfully';
|
||||
errorMessage = '';
|
||||
authTestResult = null;
|
||||
}
|
||||
|
||||
async function testAuthentication() {
|
||||
if (!$deezerAuth.arl) {
|
||||
authTestResult = 'Not logged in';
|
||||
return;
|
||||
}
|
||||
|
||||
testingAuth = true;
|
||||
authTestResult = null;
|
||||
|
||||
try {
|
||||
deezerAPI.setArl($deezerAuth.arl);
|
||||
const isValid = await deezerAPI.testAuth();
|
||||
authTestResult = isValid ? '✓ Authentication is working!' : '✗ Authentication failed';
|
||||
} catch (error) {
|
||||
authTestResult = `✗ Test failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
} finally {
|
||||
testingAuth = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="deezer-page">
|
||||
<h2>Deezer Authentication</h2>
|
||||
|
||||
{#if !$deezerAuth.loggedIn}
|
||||
<!-- Login Form -->
|
||||
<section class="window login-section">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">Login to Deezer</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<p>Enter your Deezer ARL token to authenticate:</p>
|
||||
|
||||
<div class="field-row-stacked">
|
||||
<label for="arl-input">ARL Token</label>
|
||||
<input
|
||||
id="arl-input"
|
||||
type="password"
|
||||
bind:value={arlInput}
|
||||
placeholder="192 character ARL token"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="error-message">
|
||||
⚠ {errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if successMessage}
|
||||
<div class="success-message">
|
||||
✓ {successMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="button-row">
|
||||
<button onclick={handleLogin} disabled={isLoading}>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Instructions -->
|
||||
<details class="instructions">
|
||||
<summary>How to get your ARL token</summary>
|
||||
<div class="instructions-content">
|
||||
<ol>
|
||||
<li>Open Browser</li>
|
||||
<li>Go to <strong>www.deezer.com</strong> and log into your account</li>
|
||||
<li>After logging in press <strong>F12</strong> to open up Developer Tools</li>
|
||||
<li>Go under the <strong>Application</strong> tab (if you don't see it click the double arrow)</li>
|
||||
<li>Open the <strong>cookie</strong> dropdown</li>
|
||||
<li>Select <strong>www.deezer.com</strong></li>
|
||||
<li>Find the <strong>arl</strong> cookie (It should be 192 chars long)</li>
|
||||
<li>Make sure only copy the value and not the entire cookie</li>
|
||||
<li>That's your ARL, now you can use it in the app</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
{:else}
|
||||
<!-- Logged In View -->
|
||||
<section class="window user-section">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">User Info</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<div class="user-info">
|
||||
<div class="field-row">
|
||||
<label>Name:</label>
|
||||
<span>{$deezerAuth.user?.name || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label>User ID:</label>
|
||||
<span>{$deezerAuth.user?.id || 'N/A'}</span>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label>Country:</label>
|
||||
<span>{$deezerAuth.user?.country || 'N/A'}</span>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label>HQ Streaming:</label>
|
||||
<span>{$deezerAuth.user?.can_stream_hq ? '✓ Yes' : '✗ No'}</span>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label>Lossless Streaming:</label>
|
||||
<span>{$deezerAuth.user?.can_stream_lossless ? '✓ Yes' : '✗ No'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if successMessage}
|
||||
<div class="success-message">
|
||||
✓ {successMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="button-row">
|
||||
<button onclick={handleLogout}>Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Test Authentication -->
|
||||
<section class="window test-section">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">Test Authentication</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<p>Test if your authentication is working:</p>
|
||||
|
||||
{#if authTestResult}
|
||||
<div class={authTestResult.startsWith('✓') ? 'success-message' : 'error-message'}>
|
||||
{authTestResult}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="button-row">
|
||||
<button onclick={testAuthentication} disabled={testingAuth}>
|
||||
{testingAuth ? 'Testing...' : 'Test Authentication'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.deezer-page {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-section,
|
||||
.user-section,
|
||||
.test-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.window-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.field-row-stacked {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.field-row label {
|
||||
font-weight: bold;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffcccc;
|
||||
color: #cc0000;
|
||||
padding: 8px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid #cc0000;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: #ccffcc;
|
||||
color: #008800;
|
||||
padding: 8px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid #008800;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
margin-top: 16px;
|
||||
padding: 8px;
|
||||
background-color: var(--button-shadow, #2a2a2a);
|
||||
}
|
||||
|
||||
.instructions summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.instructions-content {
|
||||
margin-top: 8px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.instructions-content ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.instructions-content li {
|
||||
margin: 6px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.instructions-content strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user