mirror of
https://github.com/markuryy/shark.git
synced 2025-12-12 19:51:01 +00:00
458 lines
12 KiB
Svelte
458 lines
12 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { goto } from '$app/navigation';
|
|
import { deezerAuth, loadDeezerAuth, saveArl, saveUser, clearDeezerAuth } from '$lib/stores/deezer';
|
|
import { deezerAPI } from '$lib/services/deezer';
|
|
import { addToQueue } from '$lib/stores/downloadQueue';
|
|
import { settings } from '$lib/stores/settings';
|
|
|
|
let arlInput = $state('');
|
|
let isLoading = $state(false);
|
|
let errorMessage = $state('');
|
|
let successMessage = $state('');
|
|
let testingAuth = $state(false);
|
|
let authTestResult = $state<string | null>(null);
|
|
|
|
// Track add to queue test
|
|
let trackIdInput = $state('3135556'); // Default: Daft Punk - One More Time
|
|
let isFetchingTrack = $state(false);
|
|
let isAddingToQueue = $state(false);
|
|
let trackInfo = $state<any>(null);
|
|
let queueStatus = $state('');
|
|
let queueError = $state('');
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async function fetchTrackInfo() {
|
|
if (!$deezerAuth.arl || !$deezerAuth.user) {
|
|
queueError = 'Not logged in';
|
|
return;
|
|
}
|
|
|
|
isFetchingTrack = true;
|
|
queueError = '';
|
|
trackInfo = null;
|
|
|
|
try {
|
|
deezerAPI.setArl($deezerAuth.arl);
|
|
const trackData = await deezerAPI.getTrack(trackIdInput);
|
|
console.log('Track data:', trackData);
|
|
|
|
if (!trackData || !trackData.SNG_ID) {
|
|
throw new Error('Track not found or invalid track ID');
|
|
}
|
|
|
|
trackInfo = trackData;
|
|
} catch (error) {
|
|
console.error('Fetch error:', error);
|
|
queueError = error instanceof Error ? error.message : 'Failed to fetch track';
|
|
} finally {
|
|
isFetchingTrack = false;
|
|
}
|
|
}
|
|
|
|
async function addTrackToQueue() {
|
|
if (!trackInfo) {
|
|
queueError = 'Please fetch track info first';
|
|
return;
|
|
}
|
|
|
|
if (!$settings.musicFolder) {
|
|
queueError = 'Please set a music folder in Settings first';
|
|
return;
|
|
}
|
|
|
|
isAddingToQueue = true;
|
|
queueStatus = '';
|
|
queueError = '';
|
|
|
|
try {
|
|
// Build track object
|
|
const track = {
|
|
id: trackInfo.SNG_ID,
|
|
title: trackInfo.SNG_TITLE,
|
|
artist: trackInfo.ART_NAME,
|
|
artistId: trackInfo.ART_ID,
|
|
artists: [trackInfo.ART_NAME],
|
|
album: trackInfo.ALB_TITLE,
|
|
albumId: trackInfo.ALB_ID,
|
|
albumArtist: trackInfo.ART_NAME,
|
|
albumArtistId: trackInfo.ART_ID,
|
|
trackNumber: trackInfo.TRACK_NUMBER || 1,
|
|
discNumber: trackInfo.DISK_NUMBER || 1,
|
|
duration: trackInfo.DURATION,
|
|
explicit: trackInfo.EXPLICIT_LYRICS === 1,
|
|
md5Origin: trackInfo.MD5_ORIGIN,
|
|
mediaVersion: trackInfo.MEDIA_VERSION,
|
|
trackToken: trackInfo.TRACK_TOKEN
|
|
};
|
|
|
|
// Add to queue
|
|
await addToQueue({
|
|
source: 'deezer',
|
|
type: 'track',
|
|
title: track.title,
|
|
artist: track.artist,
|
|
totalTracks: 1,
|
|
downloadObject: track
|
|
});
|
|
|
|
queueStatus = '✓ Added to download queue!';
|
|
|
|
// Navigate to downloads page after brief delay
|
|
setTimeout(() => {
|
|
goto('/downloads');
|
|
}, 1000);
|
|
|
|
} catch (error) {
|
|
console.error('Queue error:', error);
|
|
queueError = error instanceof Error ? error.message : 'Failed to add to queue';
|
|
} finally {
|
|
isAddingToQueue = 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>
|
|
|
|
<!-- Add Track to Queue -->
|
|
<section class="window test-section">
|
|
<div class="title-bar">
|
|
<div class="title-bar-text">Add Track to Download Queue</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<p>Add a track to the download queue:</p>
|
|
|
|
<div class="field-row-stacked">
|
|
<label for="track-id">Track ID (from Deezer URL)</label>
|
|
<input
|
|
id="track-id"
|
|
type="text"
|
|
bind:value={trackIdInput}
|
|
placeholder="e.g., 3135556"
|
|
disabled={isFetchingTrack || isAddingToQueue}
|
|
/>
|
|
<small class="help-text">Default: 3135556 (Daft Punk - One More Time)</small>
|
|
</div>
|
|
|
|
{#if trackInfo}
|
|
<div class="track-info">
|
|
<strong>{trackInfo.SNG_TITLE}</strong> by {trackInfo.ART_NAME}
|
|
<br>
|
|
<small>Album: {trackInfo.ALB_TITLE} • Duration: {Math.floor(trackInfo.DURATION / 60)}:{String(trackInfo.DURATION % 60).padStart(2, '0')}</small>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if queueStatus}
|
|
<div class="success-message">
|
|
{queueStatus}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if queueError}
|
|
<div class="error-message">
|
|
⚠ {queueError}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="button-row">
|
|
<button onclick={fetchTrackInfo} disabled={isFetchingTrack || isAddingToQueue}>
|
|
{isFetchingTrack ? 'Fetching...' : 'Fetch Track Info'}
|
|
</button>
|
|
|
|
<button onclick={addTrackToQueue} disabled={!trackInfo || isAddingToQueue || !$settings.musicFolder}>
|
|
{isAddingToQueue ? 'Adding...' : 'Add to Queue'}
|
|
</button>
|
|
</div>
|
|
|
|
{#if !$settings.musicFolder}
|
|
<p class="help-text" style="margin-top: 8px;">
|
|
⚠ Please set a music folder in Settings before adding to queue.
|
|
</p>
|
|
{/if}
|
|
</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"],
|
|
input[type="text"] {
|
|
width: 100%;
|
|
padding: 4px;
|
|
}
|
|
|
|
.help-text {
|
|
color: var(--text-color, #FFFFFF);
|
|
opacity: 0.7;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.track-info {
|
|
padding: 8px;
|
|
margin: 8px 0;
|
|
background-color: var(--button-shadow, #2a2a2a);
|
|
border: 1px solid var(--button-highlight, #606060);
|
|
}
|
|
|
|
.track-info strong {
|
|
font-weight: bold;
|
|
}
|
|
</style>
|