280 lines
9.6 KiB
JavaScript
280 lines
9.6 KiB
JavaScript
#!/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 <number> - Simulate <number> 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 <number>, 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 <number> - Simulate <number> 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);
|
||
}
|