-
Notifications
You must be signed in to change notification settings - Fork 7
/
unscrobbler.js
190 lines (165 loc) · 7.32 KB
/
unscrobbler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
function unscrobbler() {
// Get all scrobble rows & menu elements in order to modify them.
const scrobbleRows = document.querySelectorAll('.js-focus-controls-container');
const moreMenu = document.querySelectorAll('.chartlist-more-menu');
const recentTrackSection = document.querySelector('#recent-tracks-section');
const libraryTracklistSection = document.querySelectorAll('.tracklist-section');
// Where the range selection should start
let startIndex;
// Where the range selection should end
let endIndex;
// Whether the startIndex has been selected / deselected
let isStartSelected;
// Keeps the previous range of selected checkboxes
let previousRange = [];
// Whether the shift key was pressed or not while changing checkbox input
let shiftKeyPressed;
function handleShiftKey(event) {
shiftKeyPressed = event.shiftKey;
}
document.addEventListener('keydown', handleShiftKey, true);
document.addEventListener('keyup', handleShiftKey, true);
/**
* Add a checkbox to each scrobble row that'll allow it's selection for deletion.
* The scrobbles are contained inside table rows, so we'll have to create a new
* table data element for each checkbox and append it to the scrobble row.
*/
scrobbleRows.forEach((row) => {
const insertTableData = row.querySelector('.chartlist-loved, .chartlist-name');
// Create the checkbox container, which will be appended to the scrobble row.
const checkboxTableData = document.createElement('td');
checkboxTableData.className = 'chartlist-checkbox';
// Create the checkbox element, and append it to it's container.
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.name = 'unscrobble-checkbox';
checkboxTableData.appendChild(checkbox);
// When the checkbox input is changed, check if it was accompanied with the
// SHIFT keypress and thereby select range
checkbox.addEventListener('input', () => {
const checkboxes = [...document.getElementsByName('unscrobble-checkbox')];
if (shiftKeyPressed) {
const currentIndex = checkboxes.indexOf(checkbox);
if (currentIndex < startIndex) {
startIndex = currentIndex;
} else {
endIndex = currentIndex;
}
const newRange = checkboxes.slice(startIndex, endIndex + 1);
// Decides whethere the checkboxes should be selected / deselected,
// using the value of the first selected checkbox.
previousRange.map((cb) => (cb.checked = isStartSelected));
newRange.map((cb) => (cb.checked = !isStartSelected));
previousRange = newRange;
}
// (Re)Initiate values
else {
startIndex = endIndex = checkboxes.indexOf(checkbox);
isStartSelected = !checkbox.checked;
previousRange = [];
}
});
// Insert the checkbox before the track heart icon.
row.insertBefore(checkboxTableData, insertTableData);
// Add checkbox click trigger on row click event.
row.addEventListener('click', (event) => {
if (['TD', 'TR'].includes(event.target.tagName)) {
checkbox.click();
}
});
});
// Add a 'Delete selected scrobbles' button to each scrobble menu.
moreMenu.forEach((menu) => {
const listItem = document.createElement('li');
const deleteButton = document.createElement('button');
deleteButton.className = 'mimic-link dropdown-menu-clickable-item delete-selected-scrobbles-btn';
deleteButton.textContent = 'Delete selected scrobbles';
deleteButton.onclick = deleteScrobbles;
listItem.appendChild(deleteButton);
menu.appendChild(listItem);
});
// Track the state of the 'Select All' button.
let status = 'select';
// Alternate between the select & deselect tracks methods.
function selectAllHandler(button, section) {
if (status === 'select') {
selectAllTracks(section);
status = 'deselect';
button.innerText = 'Deselect All';
} else if (status === 'deselect') {
deselectAllTracks(section);
status = 'select';
button.innerText = 'Select All';
}
}
function createSelectAllButton(section) {
const selectAllBtn = document.createElement('button');
selectAllBtn.className = 'btn-secondary btn-sm select-all-btn';
selectAllBtn.textContent = 'Select All';
selectAllBtn.onclick = () => selectAllHandler(selectAllBtn, section);
return selectAllBtn;
}
// If the user is on their profile page, add 'Select All' button on the recent tracks section.
if (recentTrackSection) {
const title = recentTrackSection.querySelector('h2');
const selectAllBtn = createSelectAllButton(recentTrackSection);
title.appendChild(selectAllBtn);
}
// If the user is on library tracklist page, add 'Select All' button to each date section.
if (libraryTracklistSection[0]) {
libraryTracklistSection.forEach((section) => {
// Add the button to each date on the section.
const dateTitles = section.querySelectorAll('.date-heading');
dateTitles.forEach((title) => {
const selectAllBtn = createSelectAllButton(section);
title.appendChild(selectAllBtn);
});
});
}
// Delete the checked scrobble rows.
function deleteScrobbles() {
const checkboxes = document.getElementsByName('unscrobble-checkbox');
// Convert the checkbox NodeList to an array.
const checkboxesArr = Array.from(checkboxes);
// Create a new array that contains only the checked checkboxes.
const checkedScrobbles = checkboxesArr.filter((node) => node.checked);
// Delete the selected scrobbles.
// This is done by manually clicking on every 'Delete scrobble' button that it's scrobble has been checked.
checkedScrobbles.forEach((checkbox) => {
const scrobbleRow = checkbox.parentNode.parentNode;
const deleteBtn = scrobbleRow.querySelector('[data-ajax-form-sets-state="deleted"]');
deleteBtn.click();
});
}
// Select all tracks for the provided section
function selectAllTracks(section) {
const checkboxes = section.querySelectorAll('input[name="unscrobble-checkbox"]');
checkboxes.forEach((checkbox) => (checkbox.checked = true));
// return deselectAllTracks(section);
}
// Deselect all tracks for the provided section
function deselectAllTracks(section) {
const checkboxes = section.querySelectorAll('input[name="unscrobble-checkbox"]');
checkboxes.forEach((checkbox) => (checkbox.checked = false));
}
}
// Check if user is logged in and can use the extension on the current page
function validatePage() {
const username = document.querySelector('.auth-dropdown-menu-item strong')?.innerText;
const url = new URL(document.URL);
if (!username) alert('You are not logged in.\nPlease log in to use Last.FM Unscrobbler.');
else if (
url.pathname.endsWith(username) ||
url.pathname.endsWith(`${username}/library`) ||
// Covers the library track page
// The path includes "username/library" and between the album and the track there is an underscore ("album/_/track").
(url.pathname.includes(`${username}/library`) && url.pathname.split('/').slice(-2)[0] == '_')
)
unscrobbler();
else
alert(
'Last.FM Unscrobbler works only on:\n· Your profile page\n· Your library scrobbles page\n· An individual track scrobble history'
);
}
// Run unscrobbler once per page
if (!document.querySelector('.chartlist-checkbox')) validatePage();