Export arrest report data from Quad Cities Daily to CSV or JSON format
Size
11.2 KB
Version
1.0.1
Created
Oct 22, 2025
Updated
1 day ago
1// ==UserScript==
2// @name Arrest Report Data Exporter
3// @description Export arrest report data from Quad Cities Daily to CSV or JSON format
4// @version 1.0.1
5// @match https://*.quadcitiesdaily.com/*
6// @icon https://quadcitiesdaily.com/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Arrest Report Data Exporter loaded');
12
13 // Function to parse arrest report data from the page
14 function parseArrestReports() {
15 console.log('Starting to parse arrest reports...');
16 const arrestReports = [];
17
18 // Find all paragraph elements in the content area
19 const contentDiv = document.querySelector('.pf-content');
20 if (!contentDiv) {
21 console.error('Content area not found');
22 return arrestReports;
23 }
24
25 const paragraphs = contentDiv.querySelectorAll('p');
26 let currentReport = {};
27
28 for (let i = 0; i < paragraphs.length; i++) {
29 const text = paragraphs[i].textContent.trim();
30
31 // Check if this paragraph contains inmate name
32 if (text.startsWith('Inmate Name:')) {
33 // Save previous report if exists
34 if (currentReport.inmateName) {
35 arrestReports.push(currentReport);
36 }
37
38 // Start new report
39 currentReport = {};
40
41 // Parse the inmate info block
42 const lines = text.split('\n').map(line => line.trim()).filter(line => line);
43
44 for (const line of lines) {
45 if (line.startsWith('Inmate Name:')) {
46 currentReport.inmateName = line.replace('Inmate Name:', '').trim();
47 } else if (line.startsWith('Birth Date:')) {
48 currentReport.birthDate = line.replace('Birth Date:', '').trim();
49 } else if (line.includes('king Number:') || line.startsWith('Booking Number:')) {
50 currentReport.bookingNumber = line.replace(/.*king Number:/, '').trim();
51 } else if (line.startsWith('Agency:')) {
52 currentReport.agency = line.replace('Agency:', '').trim();
53 }
54 }
55 }
56 // Check for Time/Date
57 else if (text.startsWith('Time/Date')) {
58 const nextPara = paragraphs[i + 1];
59 if (nextPara) {
60 currentReport.timeDate = nextPara.textContent.trim();
61 }
62 }
63 // Check for Offense information
64 else if (text.includes('Offense') && (text.includes('Area') || text.includes('Statute'))) {
65 // This is a combined offense line
66 const offenseText = text;
67 currentReport.offenseDetails = offenseText;
68
69 // Try to parse structured offense data
70 if (!currentReport.offenses) {
71 currentReport.offenses = [];
72 }
73
74 // Look for offense description in the text
75 const offenseMatch = text.match(/NEW-[^A-Z]*[A-Z][^-]*/);
76 if (offenseMatch) {
77 currentReport.offenses.push({
78 description: offenseMatch[0].trim()
79 });
80 }
81 }
82 // Check for Area
83 else if (text.startsWith('Area')) {
84 const nextPara = paragraphs[i + 1];
85 if (nextPara) {
86 currentReport.area = nextPara.textContent.trim();
87 }
88 }
89 // Check for Statute
90 else if (text.startsWith('Statute')) {
91 const nextPara = paragraphs[i + 1];
92 if (nextPara) {
93 currentReport.statute = nextPara.textContent.trim();
94 }
95 }
96 // Check for Court
97 else if (text.startsWith('Court')) {
98 const courtText = text.replace('Court', '').trim();
99 const parts = courtText.split('Crime Class');
100 if (parts.length > 0) {
101 currentReport.court = parts[0].trim();
102 }
103 if (parts.length > 1) {
104 currentReport.crimeClass = parts[1].trim();
105 }
106 }
107 }
108
109 // Add the last report
110 if (currentReport.inmateName) {
111 arrestReports.push(currentReport);
112 }
113
114 console.log(`Parsed ${arrestReports.length} arrest reports:`, arrestReports);
115 return arrestReports;
116 }
117
118 // Function to convert data to CSV format
119 function convertToCSV(data) {
120 if (data.length === 0) return '';
121
122 const headers = ['Inmate Name', 'Birth Date', 'Booking Number', 'Agency', 'Time/Date', 'Area', 'Statute', 'Court', 'Crime Class', 'Offense Details'];
123 const csvRows = [headers.join(',')];
124
125 for (const report of data) {
126 const row = [
127 `"${report.inmateName || ''}"`,
128 `"${report.birthDate || ''}"`,
129 `"${report.bookingNumber || ''}"`,
130 `"${report.agency || ''}"`,
131 `"${report.timeDate || ''}"`,
132 `"${report.area || ''}"`,
133 `"${report.statute || ''}"`,
134 `"${report.court || ''}"`,
135 `"${report.crimeClass || ''}"`,
136 `"${(report.offenseDetails || '').replace(/"/g, '""')}"`
137 ];
138 csvRows.push(row.join(','));
139 }
140
141 return csvRows.join('\n');
142 }
143
144 // Function to download file
145 function downloadFile(content, filename, mimeType) {
146 const blob = new Blob([content], { type: mimeType });
147 const url = URL.createObjectURL(blob);
148 const link = document.createElement('a');
149 link.href = url;
150 link.download = filename;
151 document.body.appendChild(link);
152 link.click();
153 document.body.removeChild(link);
154 URL.revokeObjectURL(url);
155 console.log(`Downloaded ${filename}`);
156 }
157
158 // Function to create export buttons
159 function createExportButtons() {
160 console.log('Creating export buttons...');
161
162 // Check if we're on an arrest report page
163 const contentDiv = document.querySelector('.pf-content');
164 if (!contentDiv) {
165 console.log('Not an arrest report page');
166 return;
167 }
168
169 // Check if buttons already exist
170 if (document.getElementById('arrest-export-container')) {
171 console.log('Export buttons already exist');
172 return;
173 }
174
175 // Create container for export buttons
176 const container = document.createElement('div');
177 container.id = 'arrest-export-container';
178 container.style.cssText = `
179 position: fixed;
180 top: 100px;
181 right: 20px;
182 z-index: 9999;
183 background: white;
184 border: 2px solid #333;
185 border-radius: 8px;
186 padding: 15px;
187 box-shadow: 0 4px 6px rgba(0,0,0,0.1);
188 font-family: Arial, sans-serif;
189 `;
190
191 // Create title
192 const title = document.createElement('div');
193 title.textContent = 'Export Arrest Data';
194 title.style.cssText = `
195 font-weight: bold;
196 margin-bottom: 10px;
197 color: #333;
198 font-size: 14px;
199 `;
200 container.appendChild(title);
201
202 // Create CSV export button
203 const csvButton = document.createElement('button');
204 csvButton.textContent = 'Export as CSV';
205 csvButton.style.cssText = `
206 display: block;
207 width: 100%;
208 padding: 10px;
209 margin-bottom: 8px;
210 background: #4CAF50;
211 color: white;
212 border: none;
213 border-radius: 4px;
214 cursor: pointer;
215 font-size: 13px;
216 font-weight: bold;
217 `;
218 csvButton.onmouseover = () => csvButton.style.background = '#45a049';
219 csvButton.onmouseout = () => csvButton.style.background = '#4CAF50';
220 csvButton.onclick = () => {
221 const data = parseArrestReports();
222 if (data.length === 0) {
223 alert('No arrest reports found on this page');
224 return;
225 }
226 const csv = convertToCSV(data);
227 const pageTitle = document.title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
228 downloadFile(csv, `arrest_reports_${pageTitle}.csv`, 'text/csv');
229 alert(`Exported ${data.length} arrest reports to CSV`);
230 };
231 container.appendChild(csvButton);
232
233 // Create JSON export button
234 const jsonButton = document.createElement('button');
235 jsonButton.textContent = 'Export as JSON';
236 jsonButton.style.cssText = `
237 display: block;
238 width: 100%;
239 padding: 10px;
240 background: #2196F3;
241 color: white;
242 border: none;
243 border-radius: 4px;
244 cursor: pointer;
245 font-size: 13px;
246 font-weight: bold;
247 `;
248 jsonButton.onmouseover = () => jsonButton.style.background = '#0b7dda';
249 jsonButton.onmouseout = () => jsonButton.style.background = '#2196F3';
250 jsonButton.onclick = () => {
251 const data = parseArrestReports();
252 if (data.length === 0) {
253 alert('No arrest reports found on this page');
254 return;
255 }
256 const json = JSON.stringify(data, null, 2);
257 const pageTitle = document.title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
258 downloadFile(json, `arrest_reports_${pageTitle}.json`, 'application/json');
259 alert(`Exported ${data.length} arrest reports to JSON`);
260 };
261 container.appendChild(jsonButton);
262
263 // Add close button
264 const closeButton = document.createElement('button');
265 closeButton.textContent = '×';
266 closeButton.style.cssText = `
267 position: absolute;
268 top: 5px;
269 right: 5px;
270 background: transparent;
271 border: none;
272 font-size: 20px;
273 cursor: pointer;
274 color: #666;
275 width: 25px;
276 height: 25px;
277 padding: 0;
278 line-height: 1;
279 `;
280 closeButton.onmouseover = () => closeButton.style.color = '#000';
281 closeButton.onmouseout = () => closeButton.style.color = '#666';
282 closeButton.onclick = () => container.remove();
283 container.appendChild(closeButton);
284
285 document.body.appendChild(container);
286 console.log('Export buttons created successfully');
287 }
288
289 // Initialize when page is ready
290 function init() {
291 console.log('Initializing Arrest Report Data Exporter...');
292
293 // Wait for content to load
294 if (document.readyState === 'loading') {
295 document.addEventListener('DOMContentLoaded', createExportButtons);
296 } else {
297 createExportButtons();
298 }
299 }
300
301 // Start the extension
302 init();
303})();