#!/usr/bin/env node import { sendNotification, CONFIG, countAvailableItems, saveAvailabilityCount, loadLastAvailabilityCount } from './index.js'; import fs from 'fs'; console.log('šŸŽ­ SD Park Pass Monitor Mock Script'); console.log('This script simulates park pass availability changes for testing\n'); // Create mock API response function createMockApiResponse(availableCount) { const bibItems = {}; // Add the specified number of available items for Rancho Penasquitos for (let i = 0; i < availableCount; i++) { const itemId = `1805116|31336107103179||${76 + i}`; const isAvailable = i % 2 === 0; bibItems[itemId] = { "collection": "Adult - Circulation Desk", "callNumber": "CA STATE LIBRARY PARKS PASS HIKING BACKPACK", "itemId": itemId, "copy": null, "volume": null, "branch": { "name": "Rancho Penasquitos", "code": "29" }, "inSiteScope": true, "availability": { "status": isAvailable ? "AVAILABLE" : "RECENTLY_RETURNED", "circulationType": "NON_CIRCULATING", "libraryUseOnly": false, "libraryStatus": isAvailable ? "Available" : "Recently returned", "group": "AVAILABLE_ITEMS", "statusType": isAvailable ? "AVAILABLE" : "RECENTLY_RETURNED" }, "branchName": "Rancho Penasquitos", "local": false, "requestFormUrl": null }; } // Add some checked out items for Rancho Penasquitos to make it realistic for (let i = availableCount; i < availableCount + 2; i++) { const itemId = `1805116|31336107103179||${76 + i}`; bibItems[itemId] = { "collection": "Adult - Circulation Desk", "callNumber": "CA STATE LIBRARY PARKS PASS HIKING BACKPACK", "itemId": itemId, "copy": null, "volume": null, "dueDate": "2025-07-22", "branch": { "name": "Rancho Penasquitos", "code": "29" }, "inSiteScope": true, "availability": { "status": "UNAVAILABLE", "circulationType": "NON_CIRCULATING", "libraryUseOnly": false, "libraryStatus": "Checked Out", "group": "NOT_AVAILABLE_ITEMS", "statusType": "UNAVAILABLE" }, "branchName": "Rancho Penasquitos", "local": false, "requestFormUrl": null }; } // Add some items from other branches to make it realistic bibItems["1805116|31336107103252||87"] = { "collection": "Adult - Circulation Desk", "callNumber": "CA STATE LIBRARY PARKS PASS HIKING BACKPACK", "itemId": "1805116|31336107103252||87", "copy": null, "volume": null, "branch": { "name": "Central Library", "code": "7" }, "inSiteScope": true, "availability": { "status": "AVAILABLE", "circulationType": "NON_CIRCULATING", "libraryUseOnly": false, "libraryStatus": "Available", "group": "AVAILABLE_ITEMS", "statusType": "AVAILABLE" }, "branchName": "Central Library", "local": false, "requestFormUrl": null }; return { entities: { bibItems: bibItems } }; } async function simulateMockCheck(newCount) { console.log(`\nšŸ”„ Simulating ${newCount} available park passes...`); console.log(`--- Starting mock availability check ---`); console.log(`Time: ${new Date().toISOString()}`); try { // Create mock API response const mockApiResponse = createMockApiResponse(newCount); console.log(`šŸ“š Created mock API response with ${newCount} available park passes for Rancho Penasquitos`); // Count currently available items using the real function const currentCount = countAvailableItems(mockApiResponse); console.log(`Current available park passes at ${CONFIG.BRANCH_NAME}: ${currentCount}`); // Load previous count const previousCount = loadLastAvailabilityCount(); console.log(`Previous available park passes: ${previousCount}`); // Check if availability increased (new park passes became available) if (currentCount > previousCount) { const newItems = currentCount - previousCount; const title = 'SD Park Pass Available! (MOCK)'; const message = `${newItems} new park pass(es) became available at ${CONFIG.BRANCH_NAME} branch. Total available: ${currentCount}`; console.log(`šŸ“¢ Sending notification: ${message}`); await sendNotification(title, message, 'high'); } else if (currentCount < previousCount) { console.log(`Availability decreased by ${previousCount - currentCount} items`); } else { console.log('No change in availability'); } // Save current state saveAvailabilityCount(currentCount, { previousCount, branch: CONFIG.BRANCH_NAME, mock: true }); } catch (error) { console.error('Error in mock check:', error.message); // Send error notification await sendNotification( 'SD Park Pass Monitor Error (MOCK)', `Error in mock check: ${error.message}`, 'low' ); } console.log('--- Mock availability check completed ---\n'); } async function testNotification() { console.log('\nšŸ“± Testing notification system...'); await sendNotification( 'Test Notification', 'This is a test notification from the SD Park Pass Monitor mock script. If you see this, notifications are working!', 'high' ); console.log('āœ… Test notification sent'); } function showCurrentState() { console.log('\nšŸ“Š Current State:'); try { if (fs.existsSync(CONFIG.STATE_FILE)) { const data = JSON.parse(fs.readFileSync(CONFIG.STATE_FILE, 'utf8')); console.log(` Available items: ${data.availabilityCount}`); console.log(` Last updated: ${data.lastUpdated}`); console.log(` Branch: ${data.branch}`); } else { console.log(' No state file found (first run)'); } } catch (error) { console.log(' Error reading state file:', error.message); } } function resetState() { console.log('\nšŸ”„ Resetting state...'); try { if (fs.existsSync(CONFIG.STATE_FILE)) { fs.unlinkSync(CONFIG.STATE_FILE); console.log('āœ… State file deleted'); } else { console.log('ā„¹ļø No state file to delete'); } } catch (error) { console.log('āŒ Error deleting state file:', error.message); } } async function interactiveMode() { const readline = await import('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function question(query) { return new Promise(resolve => rl.question(query, resolve)); } console.log('\nšŸŽ® Interactive Mode'); console.log('Commands:'); console.log(' test - Simulate available items'); console.log(' notify - Send test notification'); console.log(' status - Show current state'); console.log(' reset - Reset state file'); console.log(' quit - Exit'); while (true) { const input = await question('\n> '); const [command, ...args] = input.trim().split(' '); switch (command.toLowerCase()) { case 'test': { const count = parseInt(args[0]) || 0; await simulateMockCheck(count); break; } case 'notify': await testNotification(); break; case 'status': showCurrentState(); break; case 'reset': resetState(); break; case 'quit': case 'exit': rl.close(); return; default: console.log('Unknown command. Try: test , notify, status, reset, or quit'); } } } // Main function async function main() { const args = process.argv.slice(2); if (args.length === 0) { // Interactive mode await interactiveMode(); } else if (args[0] === 'test' && args[1]) { // Direct test mode const count = parseInt(args[1]); showCurrentState(); await simulateMockCheck(count); } else if (args[0] === 'notify') { // Test notification await testNotification(); } else if (args[0] === 'status') { showCurrentState(); } else if (args[0] === 'reset') { resetState(); } else { console.log('Usage:'); console.log(' node mock.js - Interactive mode'); console.log(' node mock.js test - Simulate available items'); console.log(' node mock.js notify - Send test notification'); console.log(' node mock.js status - Show current state'); console.log(' node mock.js reset - Reset state file'); } } // Only run if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error); }