Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ztffn/52e0beef4f37bddcd1f446b3f8ddb335 to your computer and use it in GitHub Desktop.
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…
<!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