-
Notifications
You must be signed in to change notification settings - Fork 0
/
vndb-list-export.user.js
140 lines (126 loc) · 5.15 KB
/
vndb-list-export.user.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
// ==UserScript==
// @name VNDB List Export
// @namespace https://github.com/Vinfall/UserScripts
// @match https://vndb.org/u*
// @match https://vndb.org/u*/ulist*
// @match https://vndb.org/u*/lengthvotes
// @grant none
// @version 4.4.3
// @author Vinfall
// @license WTFPL
// @description Export VNDB user VN & length vote list to CSV
// @description:zh-cn 导出 VNDB 用户游戏列表或时长列表至 CSV
// ==/UserScript==
// Input: table selector
// Output: table data in CSV format
function getTable(selector) {
// Get table element in user list
var userListTable = document.querySelector(selector);
// Get table header
var headers = Array.from(userListTable.querySelectorAll('thead tr')).map((row) => {
return Array.from(row.querySelectorAll('td')).map((td) => {
// this is weird, should be 'th' for real
// Delete unwanted operator strings
return td.textContent.trim().replace(/▴▾|Opt/g, '');
});
});
// Get list
var userData = Array.from(userListTable.querySelectorAll('tbody tr')).map((row) => {
return Array.from(row.querySelectorAll('td')).map((td, index) => {
// Delete unwanted string
var cellData = td.textContent.trim().replace(/ 👁|▾/g, '');
// Replace full-width space with normal one
cellData = cellData.replace(/ /g, ' ');
// Delete first row only if it's ulist table
if (selector.includes('.ulist') && index === 0) {
cellData = cellData.replace(/^\d+\/\d+/, '');
}
// Wrap content with double quotes
return '"' + cellData.replace(/"/g, '""') + '"';
});
});
// Convert to CSV
var csvContent = '';
headers.forEach((row) => {
csvContent += row.join(',') + '\n';
});
// Delete leading spaces in header
csvContent = csvContent.replace(/ ,/g, ',');
// DO NOT CHANGE THE LINE ABOVE
userData.forEach((row) => {
csvContent += row.join(',') + '\n';
});
// Delete leading spaces in table body
csvContent = csvContent.replace(/\s+$/gm, '');
csvContent = csvContent.replace(/^\s*,/gm, '');
// Delete empty lines like ""
csvContent = csvContent.replace(/\n"",/gm, '\n');
csvContent = csvContent.replace(/^""$/gm, '');
csvContent = csvContent.replace(/\n\n/gm, '\n');
return csvContent;
}
function addExportButton(table, selector, fileNamePrefix) {
// Add date to export filename
// Sample ISO date: 20240204120335
var today = new Date()
.toISOString()
.replace(/[-:]|T/g, '')
.replace(/\..+/, '');
var fileName = fileNamePrefix + today + '.csv';
// Create export button
var exportButton = document.createElement('button');
exportButton.textContent = 'Export as CSV';
exportButton.id = 'exportButton';
exportButton.style.marginLeft = '2px';
exportButton.addEventListener('click', function () {
var csvContent = getTable(table);
var blob = new Blob([csvContent], {
type: 'text/csv',
});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
});
// Add button after the selector
var browseTab = document.querySelector(selector);
browseTab.parentNode.insertBefore(exportButton, browseTab.nextSibling);
}
function addLengthVotes() {
// CSS selector does not work so use almighty XPath
var xpath = "//header//nav//menu//li[contains(., 'list')]";
var listLi = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
// Modify based on cloned list element
var lengthVotesLi = listLi.cloneNode(true);
var lengthVotesA = lengthVotesLi.querySelector('a');
lengthVotesA.textContent = 'lengthvotes';
// Remove focus
lengthVotesLi.classList.remove('tabselected');
lengthVotesA.href = lengthVotesA.href.replace(/ulist.*$/g, 'lengthvotes');
listLi.parentNode.insertBefore(lengthVotesLi, listLi.nextSibling);
}
(function () {
'use strict';
// Add lengthvotes button
addLengthVotes();
var url = window.location.href;
// User list
if (url.includes('ulist')) {
var tableSelector = '.ulist.browse > table';
// Fallback to labelfilters if vanilla VNDB export button is unavailable (i.e. not login)
var buttonSelector = document.querySelector('#exportlist') ? '#exportlist' : '.submit';
addExportButton(tableSelector, buttonSelector, 'vndb-list-export-');
}
// Length votes list
else if (url.includes('lengthvotes')) {
var tableSelector = '.lengthlist.browse > table';
// Dirty fallback button if the user has so limited length votes...
var buttonSelector = document.querySelector('.browsetabs') ? '.browsetabs' : 'article > h1';
addExportButton(tableSelector, buttonSelector, 'vndb-lengthvotes-export-');
}
// Error handling, actually redundant as long as VNDB does not change those URLs
else {
console.log(url + 'is not a valid domain or currently unsupported.');
}
})();