Files
sd-park-pass-ntfy/mock.js
Aram Chia Sarafian 95dfdabebb Initial commit
2025-07-13 23:46:34 -07:00

280 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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);
}