Initial commit

This commit is contained in:
Aram Chia Sarafian
2025-07-13 23:46:34 -07:00
commit 95dfdabebb
13 changed files with 4108 additions and 0 deletions

279
mock.js Normal file
View File

@ -0,0 +1,279 @@
#!/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);
}