tangled
alpha
login
or
join now
chadtmiller.com
/
nts-teal-piper
4
fork
atom
A Chrome extension that scrobbles NTS Radio tracks to teal.fm
4
fork
atom
overview
issues
pulls
pipelines
fix login copy, show last 3 scrobbles in the popup
chadtmiller.com
5 months ago
7a279ded
faace356
+89
-6
4 changed files
expand all
collapse all
unified
split
background.js
popup.html
popup.js
styles.css
+12
background.js
···
42
42
sendResponse({ authenticated: atproto.isAuthenticated() });
43
43
} else if (message.type === "GET_CURRENT_TRACK") {
44
44
sendResponse({ track: currentTrack });
45
45
+
} else if (message.type === "GET_RECENT_TRACKS") {
46
46
+
sendResponse({ tracks: recentTracks });
45
47
}
46
48
});
47
49
···
92
94
// Add to recent tracks
93
95
addToRecentTracks(trackInfo);
94
96
97
97
+
// Notify popup of scrobble success
98
98
+
chrome.runtime.sendMessage({ type: 'SCROBBLE_SUCCESS' }).catch(() => {
99
99
+
// Ignore errors if popup is not open
100
100
+
});
101
101
+
95
102
// Show notification
96
103
chrome.notifications.create({
97
104
type: "basic",
···
113
120
114
121
// Add to recent tracks
115
122
addToRecentTracks(trackInfo);
123
123
+
124
124
+
// Notify popup of scrobble success
125
125
+
chrome.runtime.sendMessage({ type: 'SCROBBLE_SUCCESS' }).catch(() => {
126
126
+
// Ignore errors if popup is not open
127
127
+
});
116
128
117
129
chrome.notifications.create({
118
130
type: "basic",
+15
-5
popup.html
···
1
1
<!DOCTYPE html>
2
2
<html>
3
3
+
3
4
<head>
4
5
<meta charset="UTF-8">
5
6
<title>NTS Radio Scrobbler</title>
6
7
<link rel="stylesheet" href="styles.css">
7
8
</head>
9
9
+
8
10
<body>
9
11
<div class="container">
10
12
<h1>NTS Radio Scrobbler</h1>
11
13
12
14
<div id="auth-section">
13
15
<div id="login-form">
14
14
-
<h2>Login to Bluesky</h2>
16
16
+
<h2>Login to the AT Protocol</h2>
15
17
<input type="text" id="pds-url" placeholder="PDS URL (default: https://bsky.social)" />
16
18
<input type="text" id="identifier" placeholder="Handle or email" />
17
17
-
<input type="password" id="password" placeholder="Password" />
19
19
+
<input type="password" id="password" placeholder="App Password" />
18
20
<button id="login-btn">Login</button>
19
21
<div id="error-msg" class="error"></div>
20
22
</div>
21
23
22
24
<div id="logged-in" style="display: none;">
23
23
-
<h2>Connected to Bluesky</h2>
25
25
+
<h2>Connected to the AT Protocol</h2>
24
26
<p id="user-info"></p>
25
27
<button id="logout-btn">Logout</button>
26
28
</div>
···
31
33
<div id="current-track">
32
34
<p class="info">No track detected</p>
33
35
</div>
36
36
+
<div id="recent-scrobbles">
37
37
+
<h3>Recent Scrobbles</h3>
38
38
+
<div id="recent-tracks-list">
39
39
+
<p class="info small">No recent scrobbles</p>
40
40
+
</div>
41
41
+
</div>
34
42
</div>
35
43
36
44
<div id="settings-section">
···
42
50
</div>
43
51
44
52
<div id="status-section">
45
45
-
<p class="info">Visit <a href="https://www.nts.live" target="_blank">nts.live</a> and start listening to scrobble tracks!</p>
53
53
+
<p class="info">Visit <a href="https://www.nts.live" target="_blank">nts.live</a> and start listening to scrobble
54
54
+
tracks!</p>
46
55
</div>
47
56
</div>
48
57
49
58
<script src="popup.js"></script>
50
59
</body>
51
51
-
</html>
60
60
+
61
61
+
</html>
+22
-1
popup.js
···
29
29
pdsUrlInput.value = settings.pdsUrl;
30
30
}
31
31
32
32
-
// Get current track
32
32
+
// Get current track and recent scrobbles
33
33
updateCurrentTrack();
34
34
+
updateRecentScrobbles();
34
35
35
36
// Listen for track updates
36
37
chrome.runtime.onMessage.addListener((message) => {
37
38
if (message.type === 'TRACK_UPDATE') {
38
39
updateCurrentTrack();
40
40
+
} else if (message.type === 'SCROBBLE_SUCCESS') {
41
41
+
updateRecentScrobbles();
39
42
}
40
43
});
41
44
···
112
115
`;
113
116
} else {
114
117
currentTrackDiv.innerHTML = '<p class="info">No track detected</p>';
118
118
+
}
119
119
+
}
120
120
+
121
121
+
async function updateRecentScrobbles() {
122
122
+
const response = await chrome.runtime.sendMessage({ type: 'GET_RECENT_TRACKS' });
123
123
+
const recentTracksList = document.getElementById('recent-tracks-list');
124
124
+
125
125
+
if (response && response.tracks && response.tracks.length > 0) {
126
126
+
// Show only the latest 3 scrobbles
127
127
+
const latestTracks = response.tracks.slice(0, 3);
128
128
+
recentTracksList.innerHTML = latestTracks.map(track => `
129
129
+
<div class="recent-track">
130
130
+
<span class="recent-track-title">${track.track}</span>
131
131
+
<span class="recent-track-artist">${track.artist}</span>
132
132
+
</div>
133
133
+
`).join('');
134
134
+
} else {
135
135
+
recentTracksList.innerHTML = '<p class="info small">No recent scrobbles</p>';
115
136
}
116
137
}
117
138
});
+40
styles.css
···
134
134
font-size: 12px;
135
135
color: #666;
136
136
}
137
137
+
138
138
+
#recent-scrobbles {
139
139
+
margin-top: 16px;
140
140
+
}
141
141
+
142
142
+
#recent-scrobbles h3 {
143
143
+
font-size: 12px;
144
144
+
font-weight: 600;
145
145
+
color: #999;
146
146
+
margin: 0 0 8px 0;
147
147
+
text-transform: uppercase;
148
148
+
letter-spacing: 0.5px;
149
149
+
}
150
150
+
151
151
+
#recent-tracks-list {
152
152
+
display: flex;
153
153
+
flex-direction: column;
154
154
+
gap: 6px;
155
155
+
}
156
156
+
157
157
+
.recent-track {
158
158
+
display: flex;
159
159
+
flex-direction: column;
160
160
+
font-size: 11px;
161
161
+
line-height: 1.4;
162
162
+
color: #999;
163
163
+
}
164
164
+
165
165
+
.recent-track-title {
166
166
+
color: #666;
167
167
+
}
168
168
+
169
169
+
.recent-track-artist {
170
170
+
color: #999;
171
171
+
}
172
172
+
173
173
+
.info.small {
174
174
+
font-size: 11px;
175
175
+
color: #999;
176
176
+
}