Arrest Report Data Exporter

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})();
Arrest Report Data Exporter | Robomonkey