Instantly share code, notes, and snippets.
Created
June 26, 2025 18:33
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save ztffn/52e0beef4f37bddcd1f446b3f8ddb335 to your computer and use it in GitHub Desktop.
A self-contained HTML widget that displays current listening progress from your Audiobookshelf server. Shows book covers, titles, authors, and progress bars for books currently being read. Perfect for embedding in Homarr dashboards or any web interface. Simply configure your Audiobookshelf API endpoint and Bearer token, then serve the HTML file…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Audiobookshelf - Listening Progress Widget</title> | |
<style> | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: #1a1a1a; | |
color: #fff; | |
margin: 0; | |
padding: 15px; | |
overflow-x: hidden; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 20px; | |
color: #ffd93d; | |
font-size: 18px; | |
font-weight: bold; | |
} | |
.progress-item { | |
display: flex; | |
align-items: center; | |
margin-bottom: 15px; | |
padding: 10px; | |
background: #2a2a2a; | |
border-radius: 8px; | |
border-left: 4px solid #4ecdc4; | |
} | |
.cover { | |
width: 60px; | |
height: 60px; | |
border-radius: 6px; | |
background: #3a3a3a; | |
margin-right: 15px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: #888; | |
font-size: 12px; | |
text-align: center; | |
overflow: hidden; | |
} | |
.cover img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
border-radius: 6px; | |
} | |
.info { | |
flex: 1; | |
min-width: 0; | |
} | |
.title { | |
font-size: 14px; | |
font-weight: bold; | |
color: #fff; | |
margin-bottom: 4px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.author { | |
font-size: 12px; | |
color: #aaa; | |
margin-bottom: 8px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
.progress-bar { | |
width: 100%; | |
height: 6px; | |
background: #444; | |
border-radius: 3px; | |
overflow: hidden; | |
margin-bottom: 4px; | |
} | |
.progress-fill { | |
height: 100%; | |
background: linear-gradient(90deg, #4ecdc4, #00d4aa); | |
border-radius: 3px; | |
transition: width 0.3s ease; | |
} | |
.progress-text { | |
font-size: 11px; | |
color: #888; | |
} | |
.error { | |
text-align: center; | |
color: #ff6b6b; | |
font-size: 14px; | |
padding: 20px; | |
} | |
.loading { | |
text-align: center; | |
color: #888; | |
font-size: 14px; | |
padding: 20px; | |
} | |
.config-note { | |
background: #2a2a2a; | |
border: 1px solid #444; | |
border-radius: 8px; | |
padding: 15px; | |
margin-bottom: 20px; | |
font-size: 12px; | |
color: #ccc; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="header">📚 Audiobookshelf Listening Progress</div> | |
<!-- Configuration Instructions (remove this section after setup) --> | |
<div class="config-note" id="configNote"> | |
<strong>⚙️ Setup Required:</strong><br> | |
1. Get your API token from Audiobookshelf (Config → Users → Your Account)<br> | |
2. Update <code>API_BASE_URL</code> and <code>API_TOKEN</code> in the JavaScript below<br> | |
3. Remove this instruction box<br> | |
<em>Note: For CORS issues, set up a reverse proxy route or use direct API URL</em> | |
</div> | |
<div id="content"> | |
<div class="loading">Loading listening progress...</div> | |
</div> | |
<script> | |
// 🔧 CONFIGURATION - Update these values for your setup | |
const API_BASE_URL = "https://your-audiobookshelf-server.com/api"; // Your Audiobookshelf API URL | |
const API_TOKEN = "YOUR_API_TOKEN_HERE"; // Get from Audiobookshelf: Config → Users → Your Account | |
// 📊 WIDGET SETTINGS | |
const MAX_BOOKS = 4; // Number of books to display | |
const REFRESH_INTERVAL = 30; // Refresh interval in seconds | |
async function fetchListeningProgress() { | |
const content = document.getElementById('content'); | |
try { | |
// Check if configuration is needed | |
if (API_TOKEN === "YOUR_API_TOKEN_HERE" || API_BASE_URL.includes("your-audiobookshelf-server.com")) { | |
content.innerHTML = ` | |
<div class="error"> | |
<p>⚠️ Configuration Required</p> | |
<p style="font-size: 12px; margin-top: 10px;"> | |
1. Log into Audiobookshelf as admin<br> | |
2. Go to <strong>Config → Users → Your Account</strong><br> | |
3. Copy your API token<br> | |
4. Update <code>API_TOKEN</code> and <code>API_BASE_URL</code> in this file | |
</p> | |
</div> | |
`; | |
return; | |
} | |
// Fetch books in progress | |
const response = await fetch(`${API_BASE_URL}/me/items-in-progress?limit=${MAX_BOOKS}`, { | |
headers: { | |
'Authorization': `Bearer ${API_TOKEN}`, | |
'Content-Type': 'application/json' | |
} | |
}); | |
if (!response.ok) { | |
throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
} | |
const data = await response.json(); | |
if (!data.libraryItems || data.libraryItems.length === 0) { | |
content.innerHTML = '<div class="error">📚 No books in progress</div>'; | |
return; | |
} | |
// Generate HTML for each book | |
let html = ''; | |
for (const item of data.libraryItems) { | |
// Get detailed progress for each book | |
let progress = 0; | |
try { | |
const progressResp = await fetch(`${API_BASE_URL}/me/progress/${item.id}`, { | |
headers: { | |
'Authorization': `Bearer ${API_TOKEN}`, | |
'Content-Type': 'application/json' | |
} | |
}); | |
if (progressResp.ok) { | |
const progressData = await progressResp.json(); | |
progress = Math.round(progressData.progress * 100); | |
} | |
} catch (e) { | |
console.warn('Could not fetch progress for', item.id); | |
} | |
// Build cover image URL | |
const coverUrl = item.media.coverPath ? | |
`${API_BASE_URL}/items/${item.id}/cover` : null; | |
html += ` | |
<div class="progress-item"> | |
<div class="cover"> | |
${coverUrl ? | |
`<img src="${coverUrl}" alt="Cover" onerror="this.parentElement.innerHTML='📖'">` : | |
'📖' | |
} | |
</div> | |
<div class="info"> | |
<div class="title">${escapeHtml(item.media.metadata.title || 'Unknown Title')}</div> | |
<div class="author">${escapeHtml(item.media.metadata.authorName || 'Unknown Author')}</div> | |
<div class="progress-bar"> | |
<div class="progress-fill" style="width: ${progress}%"></div> | |
</div> | |
<div class="progress-text">${progress}% complete</div> | |
</div> | |
</div> | |
`; | |
} | |
content.innerHTML = html; | |
// Hide config note after successful load | |
const configNote = document.getElementById('configNote'); | |
if (configNote) configNote.style.display = 'none'; | |
} catch (error) { | |
console.error('Error fetching listening progress:', error); | |
content.innerHTML = ` | |
<div class="error"> | |
<p>❌ Failed to load listening progress</p> | |
<p style="font-size: 12px; margin-top: 10px;"> | |
<strong>Error:</strong> ${error.message}<br> | |
<em>Check API URL, token, and network connectivity</em> | |
</p> | |
</div> | |
`; | |
} | |
} | |
function escapeHtml(text) { | |
const div = document.createElement('div'); | |
div.textContent = text; | |
return div.innerHTML; | |
} | |
// Initialize widget | |
fetchListeningProgress(); | |
// Auto-refresh | |
setInterval(fetchListeningProgress, REFRESH_INTERVAL * 1000); | |
console.log('📚 Audiobookshelf Progress Widget loaded - Refresh every', REFRESH_INTERVAL, 'seconds'); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment