From 7fd07c322b92d05a4bcc070ef0b5ec6f71700d16 Mon Sep 17 00:00:00 2001 From: Isaac Godin Date: Thu, 15 Jan 2026 09:44:36 -0500 Subject: [PATCH 01/11] New Ticket System will need to decide if its better or worse than old one before pushing to main --- src/main.js | 55 ---------- src/modules/sdp-api.js | 231 ----------------------------------------- src/test.js | 152 +++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 286 deletions(-) delete mode 100644 src/main.js delete mode 100644 src/modules/sdp-api.js create mode 100644 src/test.js diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 62492a8..0000000 --- a/src/main.js +++ /dev/null @@ -1,55 +0,0 @@ -/* SDP Request Watcher - * Monitors requests in Zoho's SDP Cloud platform to play a sound alert - * when a request is closed. - * - * Developed for MCCS ITS by Skylar Grant - */ - -const dotenv = require('dotenv'); -dotenv.config({ quiet: true }); -const { CLIENT_ID, CLIENT_SECRET } = process.env; -const { Client, ListInfoBuilder, SearchCriteriaBuilder } = require('./modules/sdp-api'); -const client = new Client(CLIENT_ID, CLIENT_SECRET); -const technicianNamesArray = process.env.TECHNICIAN_NAMES.split(','); -const statusNamesArray = process.env.STATUS_NAMES.split(','); - -async function authInitLoop() { - await client.init(); - console.log(`Please visit ${client.verificationUrl} and enter the code: ${client.userCode}`); - - // Poll for authorization - const pollInterval = setInterval(async () => { - try { - /** @type {boolean} */ - const authorized = await client.poll(); - if (authorized) { - clearInterval(pollInterval); - console.log('Authorization successful! Access token obtained.'); - } else { - console.log('Still waiting for user authorization...'); - } - } catch (error) { - clearInterval(pollInterval); - console.error('Error during polling:', error.message); - } - }, client.pollInterval + 500); -} - -async function pullRequests() { - try { - const listInfo = new ListInfoBuilder() - .setRowCount(10); - - const searchCriteria = new SearchCriteriaBuilder() - .addTechnicians(technicianNamesArray, "is") - .addStatuses(statusNamesArray, "is", "and"); - - listInfo.data.search_criteria = searchCriteria.data; - } catch (error) { - console.error('Error pulling requests:', error.message); - } -} - -authInitLoop().catch(err => { - console.error('Error in authInitLoop execution:', err.message); -}); \ No newline at end of file diff --git a/src/modules/sdp-api.js b/src/modules/sdp-api.js deleted file mode 100644 index 13d2582..0000000 --- a/src/modules/sdp-api.js +++ /dev/null @@ -1,231 +0,0 @@ -/* SDP-API Tool - * Client for interacting with the Zoho SDP Cloud API. - * - * Developed for MCCS ITS by Skylar Grant - */ -const axios = require('axios'); - -// Template strings for requests -const strings = { - tokenPrefix: 'Authorization: Zoho-oauthtoken ', - initiation: { - endpoint: 'https://accounts.zoho.com/oauth/v3/device/code', - params: { - grant_type: 'device_request', - scope: 'SDPOnDemand.requests.READ', - access_type: 'offline' - } - }, - polling: { - endpoint: 'https://accounts.zoho.com/oauth/v3/device/token', - params: { - grant_type: 'device_token' - } - } -} - -/** SDP API Client - * Handles OAuth device authorization flow for Zoho's SDP Cloud API. - * @class Client - * @constructor - * @param {string} clientId - The Client ID for the Zoho application. - * @param {string} clientSecret - The Client Secret for the Zoho application. - * @returns {Client} An instance of the SDP API Client. - */ -class Client { - constructor(clientId, clientSecret) { - if (!clientId || !clientSecret) { - throw new Error('Client ID and Client Secret are required to create an SDP API Client.'); - } - this.clientId = clientId; - this.clientSecret = clientSecret; - - // Set up placeholders for tokens and codes - this.deviceCode = null; - this.accessToken = null; - this.refreshToken = null; - this.userCode = null; - this.verificationUrl = null; - - // Create copies of the request parameter templates - this.initParams = JSON.parse(JSON.stringify(strings.initiation.params)); - this.initParams.client_id = this.clientId; - - this.pollParams = JSON.parse(JSON.stringify(strings.polling.params)); - this.pollParams.client_id = this.clientId; - this.pollParams.client_secret = this.clientSecret; - - return this; - } - - async init() { - const urlParams = new URLSearchParams(this.initParams).toString(); - const fullUrl = `${strings.initiation.endpoint}?${urlParams}`; - const initRes = await axios.post(fullUrl); - - if (initRes.status !== 200) { - throw new Error(`Error during initiation request: ${initRes.status} - ${initRes.statusText}`); - } - - this.deviceCode = initRes.data.device_code; - this.userCode = initRes.data.user_code; - this.verificationUrl = initRes.data.verification_uri_complete; - this.pollInterval = initRes.data.interval; - - return this; - } - - async poll() { - if (!this.deviceCode) { - throw new Error('Device code is not set. Please run the initiation request first.'); - } - this.pollParams.code = this.deviceCode; - const pollParams = new URLSearchParams(this.pollParams).toString(); - const fullPollUrl = `${strings.polling.endpoint}?${pollParams}`; - const pollRes = await axios.post(fullPollUrl); - if (pollRes.status !== 200) { - throw new Error(`Error during polling request: ${pollRes.status} - ${pollRes.statusText}`); - } - - if (pollRes.data.access_token) { - this.accessToken = pollRes.data.access_token; - this.refreshToken = pollRes.data.refresh_token; - return true; - } else { - return false; - } - } -} - -/** Search Criteria Builder - * Class to help build search criteria for SDP API requests. - * @class SearchCriteriaBuilder - * @constructor - * @returns {SearchCriteriaBuilder} An instance of the Search Criteria Builder. - */ -class SearchCriteriaBuilder { - constructor() { - this.data = new Array(); - return this; - } - - /** Add technician names to the search criteria. - * @param {Array} technicianNamesArray Array of technician names - * @param {String} condition Condition to use for filtering, "is" or "is not" (Default: "is") - * @param {String} operator Operator to use for filtering, "and" or "or" (Optional if first/only criterion) - * @returns {SearchCriteriaBuilder} - */ - addTechnicians(technicianNamesArray, condition, operator) { - if (!Array.isArray(technicianNamesArray) || technicianNamesArray.length === 0) { - throw new Error('Technician names must be a non-empty array.'); - } - condition = condition || 'is'; - - const newCriterion = { - field: 'technician.name', - condition: condition, - values: technicianNamesArray - }; - - if (operator) { - newCriterion.logical_operator = operator; - } - - this.data.push(newCriterion); - return this; - } - - /** Add status names to the search criteria. - * @param {Array} statusesArray Array of status names - * @param {String} condition Condition to use for filtering, "is" or "is not" (Default: "is not") - * @param {String} operator Operator to use for filtering, "and" or "or" (Optional if first/only criterion) - * @returns {SearchCriteriaBuilder} - */ - addStatuses(statusesArray, condition, operator) { - if (!Array.isArray(statusesArray) || statusesArray.length === 0) { - throw new Error('Statuses must be a non-empty array.'); - } - condition = condition || 'is not'; - - const newCriterion = { - field: 'status.internal_name', - condition: condition, - values: statusesArray - }; - if (operator) { - newCriterion.logical_operator = operator; - } - this.data.push(newCriterion); - return this; - } -} - -/** List Info Builder - * Class to help build list info for SDP API requests. - * @class ListInfoBuilder - * @constructor - * @returns {ListInfoBuilder} An instance of the List Info Builder. - */ -class ListInfoBuilder { - constructor() { - this.data = { - row_count: 100, - start_index: 0, - sort_field: 'last_updated_time', - sort_order: 'desc', - get_total_count: true - }; - return this; - } - - setRowCount(count) { - if (typeof count !== 'number' || count <= 0) { - throw new Error('Row count must be a positive number.'); - } - this.data.row_count = count; - return this; - } - - setStartIndex(index) { - if (typeof index !== 'number' || index < 0) { - throw new Error('Start index must be a non-negative number.'); - } - this.data.start_index = index; - return this; - } - - setSortField(field) { - if (typeof field !== 'string' || field.trim() === '') { - throw new Error('Sort field must be a non-empty string.'); - } - this.data.sort_field = field; - return this; - } - - setSortOrder(order) { - if (order !== 'asc' && order !== 'desc') { - throw new Error('Sort order must be either "asc" or "desc".'); - } - this.data.sort_order = order; - return this; - } - - enableTotalCount(enable) { - this.data.get_total_count = Boolean(enable); - return this; - } - - setSearchCriteria(criteriaArray) { - if (!Array.isArray(criteriaArray)) { - throw new Error('Search criteria must be an array.'); - } - this.data.search_criteria = criteriaArray; - return this; - } -} - -module.exports = { - Client, - SearchCriteriaBuilder, - ListInfoBuilder -} \ No newline at end of file diff --git a/src/test.js b/src/test.js new file mode 100644 index 0000000..2885144 --- /dev/null +++ b/src/test.js @@ -0,0 +1,152 @@ +//I barely understand this code, so don't ask me to explain it. +// Or do just know it will be very basic explanations. + +require('dotenv').config({ quiet: true }); +const axios = require('axios'); + +const { CLIENT_ID, CLIENT_SECRET, TECHNICIAN_NAMES, STATUS_NAMES } = process.env; + +if (!CLIENT_ID || !CLIENT_SECRET) { + console.error('Client id and secret missing from .env'); + process.exit(1); +} + +const technicians = TECHNICIAN_NAMES ? TECHNICIAN_NAMES.split(',') : []; +const statuses = STATUS_NAMES ? STATUS_NAMES.split(',') : []; + +let accessToken = null; + +async function getDeviceCode() { + const params = new URLSearchParams({ + client_id: CLIENT_ID, + scope: 'SDPOnDemand.requests.READ', + grant_type: 'device_request', + access_type: 'offline' + }); + + const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/code', params); + return res.data; +} + +async function pollForToken(deviceCode, interval) { + const pollParams = new URLSearchParams({ + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + code: deviceCode, + grant_type: 'device_token' + }); + + return new Promise((resolve, reject) => { + const pollInterval = setInterval(async () => { + try { + const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/token', pollParams); + if (res.data.access_token) { + clearInterval(pollInterval); + accessToken = res.data.access_token; + console.log('Authorization successful'); + resolve(); + } + } catch (err) { + + } + }, (interval + 500)); + }); +} + +async function fetchClosedTickets() { + if (!accessToken) return; + + try { + const tenMinutesAgo = Date.now() - (10 * 60 * 1000); + + const listInfo = { + list_info: { + row_count: 50, + sort_field: "completed_time", + sort_order: "desc", + fields_required: ["display_id", "subject", "status", "completed_time", "technician"], + search_criteria: [ + { + field: "status.name", + condition: "is", + value: "Closed" + }, + { + field: "completed_time", + condition: "greater_than", + value: tenMinutesAgo.toString(), + logical_operator: "and" + } + ] + } + }; + + const response = await axios.get( + 'https://ithelp.mainecc.edu/api/v3/requests', + { + params: { input_data: JSON.stringify(listInfo) }, + headers: { + 'Authorization': `Zoho-oauthtoken ${accessToken}`, + 'Accept': 'application/vnd.manageengine.sdp.v3+json' + } + } + ); + + let tickets = response.data?.requests || []; + + const trimmedTechnicians = technicians.map(t => t.trim()); + + tickets = tickets.filter(ticket => { + if (!ticket.completed_time){return false;} + const completedTime = typeof ticket.completed_time === 'object' + ? ticket.completed_time.value + : ticket.completed_time; + if (completedTime <= tenMinutesAgo){return false;} + + const technicianName = ticket.technician ? ticket.technician.name : null; + if (!technicianName){return false;} + return trimmedTechnicians.includes(technicianName); + }); + + console.log(`\nCheck ran at ${new Date().toLocaleTimeString()}`); + + + + + if (tickets.length === 0) { + console.log("No tickets closed in the last 10 minutes."); + } else { + console.log(`Found ${tickets.length} tickets closed since ${new Date(tenMinutesAgo).toLocaleTimeString()}:`); + console.log('---------------------------------------------------------'); + + tickets.forEach((ticket) => { + const ticketId = ticket.display_id; + const subject = ticket.subject; + const tech = ticket.technician ? ticket.technician.name : 'Unassigned'; + const closedAt = ticket.completed_time ? ticket.completed_time.display_value : 'N/A'; + + console.log(`ID: ${ticketId} - ${subject}`); + console.log(`Technician: ${tech}`); + console.log(`Closed At: ${closedAt}`); + console.log('---------------------------------------------------------'); + }); + } + + } catch (err) { + console.error('Error: ', err.response?.data || err.message); + } +} + + +async function main() { + const deviceData = await getDeviceCode(); + console.log('Open this URL and authorize:', deviceData.verification_uri_complete); + console.log('Code: ', deviceData.user_code); + + await pollForToken(deviceData.device_code, deviceData.interval); + + setInterval(fetchClosedTickets, 600000); //runs every 10 mins + fetchClosedTickets(); +} + +main().catch(err => console.error(err)); -- 2.45.2 From 9cf1c6ac37ad02a532d4d9e285ca637ba26b10be Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Jul 2026 14:06:52 -0400 Subject: [PATCH 02/11] Reads all statuses and users now also updated the repo to contain a simple setup guide and an example .env --- README.md | 11 ++++++++++- src/env.example | 4 ++++ src/test.js | 6 +++--- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 src/env.example diff --git a/README.md b/README.md index f45f01f..5b3cbde 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # sdp-request-watcher -A locally run JS app to monitor for request closures using Zoho's SDP Cloud API. + +# Setup +env.example contains the setup for the env requires a Non-Browser Application key from https://api-console.zoho.com/add + +Technicians and Statuses are checked in the .env file + +I resolved the issue with it only finding closed tickets so it should now find resolved and closed I havent tested with others but will + +The technician field can have spaces after the comma however the statuses can not. There can be no spaces before the status or it wont work properly. + ## Reference Materials diff --git a/src/env.example b/src/env.example new file mode 100644 index 0000000..b1fd72e --- /dev/null +++ b/src/env.example @@ -0,0 +1,4 @@ +CLIENT_ID= +CLIENT_SECRET= +TECHNICIAN_NAMES=First Last, First Last +STATUS_NAMES=Closed,Resolved \ No newline at end of file diff --git a/src/test.js b/src/test.js index 2885144..4013add 100644 --- a/src/test.js +++ b/src/test.js @@ -67,9 +67,9 @@ async function fetchClosedTickets() { fields_required: ["display_id", "subject", "status", "completed_time", "technician"], search_criteria: [ { - field: "status.name", + field: "status.internal_name", condition: "is", - value: "Closed" + values: statuses.map(status => status.trim()) }, { field: "completed_time", @@ -145,7 +145,7 @@ async function main() { await pollForToken(deviceData.device_code, deviceData.interval); - setInterval(fetchClosedTickets, 600000); //runs every 10 mins + setInterval(fetchClosedTickets, 1 * 60 * 1000); //Only change the first number the first is the amount of minutes between each run fetchClosedTickets(); } -- 2.45.2 From 7a59fd0cd4783f4787e3ae7ecf8009f35965ef87 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Jul 2026 14:33:41 -0400 Subject: [PATCH 03/11] Gave claude my back end and it came up with this front end --- package-lock.json | 8 +- package.json | 2 +- src/public/index.html | 446 ++++++++++++++++++++++++++++++++++++++++++ src/server.js | 222 +++++++++++++++++++++ src/test.js | 152 -------------- 5 files changed, 673 insertions(+), 157 deletions(-) create mode 100644 src/public/index.html create mode 100644 src/server.js delete mode 100644 src/test.js diff --git a/package-lock.json b/package-lock.json index 0bcfd87..f23f9b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "dependencies": { "axios": "^1.13.2", - "dotenv": "^17.2.3" + "dotenv": "^17.4.2" } }, "node_modules/asynckit": { @@ -61,9 +61,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 15f3cd4..3d2a034 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { "axios": "^1.13.2", - "dotenv": "^17.2.3" + "dotenv": "^17.4.2" } } diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000..f8af0ee --- /dev/null +++ b/src/public/index.html @@ -0,0 +1,446 @@ + + + + + +Closed ticket board + + + + + + +
+ +
+

Closed ticket board

+
+ Not connected +
+
+ +
+ +
+ +
+
+ + + + diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..b830c77 --- /dev/null +++ b/src/server.js @@ -0,0 +1,222 @@ + +require('dotenv').config({ quiet: true }); + +const path = require('path'); +const express = require('express'); +const axios = require('axios'); + +const { CLIENT_ID, CLIENT_SECRET, TECHNICIAN_NAMES, STATUS_NAMES } = process.env; +const PORT = process.env.PORT || 3000; + +if (!CLIENT_ID || !CLIENT_SECRET) { + console.error('Client id and secret missing from .env'); + process.exit(1); +} + +const technicians = (TECHNICIAN_NAMES ? TECHNICIAN_NAMES.split(',') : []).map(t => t.trim()).filter(Boolean); +const statuses = (STATUS_NAMES ? STATUS_NAMES.split(',') : []).map(s => s.trim()).filter(Boolean); + +const WINDOW_MINUTES = 10; +const CHECK_MINUTES = 1; + +let accessToken = null; + +const state = { + auth: 'disconnected', + verificationUrl: null, + userCode: null, + lastCheck: null, + lastError: null +}; + +const tickets = new Map(); + +async function getDeviceCode() { + const params = new URLSearchParams({ + client_id: CLIENT_ID, + scope: 'SDPOnDemand.requests.READ', + grant_type: 'device_request', + access_type: 'offline' + }); + + const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/code', params); + return res.data; +} + +function pollForToken(deviceCode, interval, expiresIn) { + const pollParams = new URLSearchParams({ + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + code: deviceCode, + grant_type: 'device_token' + }); + + return new Promise((resolve, reject) => { + const deadline = Date.now() + (expiresIn || 300) * 1000; + + const timer = setInterval(async () => { + if (Date.now() > deadline) { + clearInterval(timer); + reject(new Error('The authorization code expired. Connect again.')); + return; + } + + try { + const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/token', pollParams); + if (res.data.access_token) { + clearInterval(timer); + accessToken = res.data.access_token; + console.log('Authorization successful'); + resolve(); + } + } catch (err) { + } + }, interval + 500); + }); +} + +function toMillis(completedTime) { + if (!completedTime) return null; + const raw = typeof completedTime === 'object' ? completedTime.value : completedTime; + const ms = Number(raw); + return Number.isFinite(ms) ? ms : null; +} + +async function fetchClosedTickets() { + if (!accessToken) return; + + try { + const windowStart = Date.now() - WINDOW_MINUTES * 60 * 1000; + + const listInfo = { + list_info: { + row_count: 50, + sort_field: 'completed_time', + sort_order: 'desc', + fields_required: ['display_id', 'subject', 'status', 'completed_time', 'technician'], + search_criteria: [ + { + field: 'status.internal_name', + condition: 'is', + values: statuses.length ? statuses : ['Closed'] + }, + { + field: 'completed_time', + condition: 'greater_than', + value: windowStart.toString(), + logical_operator: 'and' + } + ] + } + }; + + const response = await axios.get('https://ithelp.mainecc.edu/api/v3/requests', { + params: { input_data: JSON.stringify(listInfo) }, + headers: { + Authorization: `Zoho-oauthtoken ${accessToken}`, + Accept: 'application/vnd.manageengine.sdp.v3+json' + } + }); + + const found = response.data?.requests || []; + + for (const ticket of found) { + const closedAt = toMillis(ticket.completed_time); + if (!closedAt || closedAt <= windowStart) continue; + + const technician = ticket.technician ? ticket.technician.name : null; + if (!technician) continue; + if (technicians.length && !technicians.includes(technician)) continue; + + tickets.set(String(ticket.display_id), { + id: ticket.display_id, + subject: ticket.subject, + technician, + status: ticket.status ? ticket.status.name : null, + closedAt, + closedDisplay: ticket.completed_time?.display_value || null + }); + } + + const cutoff = Date.now() - 12 * 60 * 60 * 1000; + for (const [key, ticket] of tickets) { + if (ticket.closedAt < cutoff) tickets.delete(key); + } + + state.lastCheck = Date.now(); + state.lastError = null; + } catch (err) { + const detail = err.response?.data; + state.lastError = detail?.response_status?.messages?.[0]?.message + || (typeof detail === 'string' ? detail : null) + || err.message; + state.lastCheck = Date.now(); + console.error('Error: ', detail || err.message); + } +} + +async function connect() { + state.auth = 'pending'; + state.lastError = null; + + const deviceData = await getDeviceCode(); + state.verificationUrl = deviceData.verification_uri_complete; + state.userCode = deviceData.user_code; + + console.log('Open this URL and authorize:', deviceData.verification_uri_complete); + console.log('Code: ', deviceData.user_code); + + try { + await pollForToken(deviceData.device_code, deviceData.interval, deviceData.expires_in); + } catch (err) { + state.auth = 'error'; + state.lastError = err.message; + state.verificationUrl = null; + state.userCode = null; + return; + } + + state.auth = 'connected'; + state.verificationUrl = null; + state.userCode = null; + + await fetchClosedTickets(); + setInterval(fetchClosedTickets, CHECK_MINUTES * 60 * 1000); +} + +const app = express(); +app.use(express.static(path.join(__dirname, 'public'))); + +app.get('/api/state', (req, res) => { + res.json({ + ...state, + windowMinutes: WINDOW_MINUTES, + checkMinutes: CHECK_MINUTES, + watching: technicians, + serverTime: Date.now(), + tickets: [...tickets.values()].sort((a, b) => b.closedAt - a.closedAt) + }); +}); + +app.post('/api/connect', async (req, res) => { + if (state.auth === 'pending' || state.auth === 'connected') { + return res.json({ ok: true }); + } + try { + connect(); + res.json({ ok: true }); + } catch (err) { + state.auth = 'error'; + state.lastError = err.message; + res.status(500).json({ ok: false, error: err.message }); + } +}); + +app.post('/api/check', async (req, res) => { + await fetchClosedTickets(); + res.json({ ok: true }); +}); + +app.listen(PORT, () => { + console.log(`Open http://localhost:${PORT}`); +}); diff --git a/src/test.js b/src/test.js deleted file mode 100644 index 4013add..0000000 --- a/src/test.js +++ /dev/null @@ -1,152 +0,0 @@ -//I barely understand this code, so don't ask me to explain it. -// Or do just know it will be very basic explanations. - -require('dotenv').config({ quiet: true }); -const axios = require('axios'); - -const { CLIENT_ID, CLIENT_SECRET, TECHNICIAN_NAMES, STATUS_NAMES } = process.env; - -if (!CLIENT_ID || !CLIENT_SECRET) { - console.error('Client id and secret missing from .env'); - process.exit(1); -} - -const technicians = TECHNICIAN_NAMES ? TECHNICIAN_NAMES.split(',') : []; -const statuses = STATUS_NAMES ? STATUS_NAMES.split(',') : []; - -let accessToken = null; - -async function getDeviceCode() { - const params = new URLSearchParams({ - client_id: CLIENT_ID, - scope: 'SDPOnDemand.requests.READ', - grant_type: 'device_request', - access_type: 'offline' - }); - - const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/code', params); - return res.data; -} - -async function pollForToken(deviceCode, interval) { - const pollParams = new URLSearchParams({ - client_id: CLIENT_ID, - client_secret: CLIENT_SECRET, - code: deviceCode, - grant_type: 'device_token' - }); - - return new Promise((resolve, reject) => { - const pollInterval = setInterval(async () => { - try { - const res = await axios.post('https://accounts.zoho.com/oauth/v3/device/token', pollParams); - if (res.data.access_token) { - clearInterval(pollInterval); - accessToken = res.data.access_token; - console.log('Authorization successful'); - resolve(); - } - } catch (err) { - - } - }, (interval + 500)); - }); -} - -async function fetchClosedTickets() { - if (!accessToken) return; - - try { - const tenMinutesAgo = Date.now() - (10 * 60 * 1000); - - const listInfo = { - list_info: { - row_count: 50, - sort_field: "completed_time", - sort_order: "desc", - fields_required: ["display_id", "subject", "status", "completed_time", "technician"], - search_criteria: [ - { - field: "status.internal_name", - condition: "is", - values: statuses.map(status => status.trim()) - }, - { - field: "completed_time", - condition: "greater_than", - value: tenMinutesAgo.toString(), - logical_operator: "and" - } - ] - } - }; - - const response = await axios.get( - 'https://ithelp.mainecc.edu/api/v3/requests', - { - params: { input_data: JSON.stringify(listInfo) }, - headers: { - 'Authorization': `Zoho-oauthtoken ${accessToken}`, - 'Accept': 'application/vnd.manageengine.sdp.v3+json' - } - } - ); - - let tickets = response.data?.requests || []; - - const trimmedTechnicians = technicians.map(t => t.trim()); - - tickets = tickets.filter(ticket => { - if (!ticket.completed_time){return false;} - const completedTime = typeof ticket.completed_time === 'object' - ? ticket.completed_time.value - : ticket.completed_time; - if (completedTime <= tenMinutesAgo){return false;} - - const technicianName = ticket.technician ? ticket.technician.name : null; - if (!technicianName){return false;} - return trimmedTechnicians.includes(technicianName); - }); - - console.log(`\nCheck ran at ${new Date().toLocaleTimeString()}`); - - - - - if (tickets.length === 0) { - console.log("No tickets closed in the last 10 minutes."); - } else { - console.log(`Found ${tickets.length} tickets closed since ${new Date(tenMinutesAgo).toLocaleTimeString()}:`); - console.log('---------------------------------------------------------'); - - tickets.forEach((ticket) => { - const ticketId = ticket.display_id; - const subject = ticket.subject; - const tech = ticket.technician ? ticket.technician.name : 'Unassigned'; - const closedAt = ticket.completed_time ? ticket.completed_time.display_value : 'N/A'; - - console.log(`ID: ${ticketId} - ${subject}`); - console.log(`Technician: ${tech}`); - console.log(`Closed At: ${closedAt}`); - console.log('---------------------------------------------------------'); - }); - } - - } catch (err) { - console.error('Error: ', err.response?.data || err.message); - } -} - - -async function main() { - const deviceData = await getDeviceCode(); - console.log('Open this URL and authorize:', deviceData.verification_uri_complete); - console.log('Code: ', deviceData.user_code); - - await pollForToken(deviceData.device_code, deviceData.interval); - - setInterval(fetchClosedTickets, 1 * 60 * 1000); //Only change the first number the first is the amount of minutes between each run - fetchClosedTickets(); -} - -main().catch(err => console.error(err)); -- 2.45.2 From a6796b343c187fee46e78ba3c4342c4273e1e8ca Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Jul 2026 14:58:30 -0400 Subject: [PATCH 04/11] Refixed the resolved tickets not showing after claude killed it. And added a sound whenever new tickets appear. It is in the script section of the html page not in the JS script as it would not work there. --- package-lock.json | 780 +++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- src/public/ding.mp3 | Bin 0 -> 50501 bytes src/public/index.html | 15 + src/server.js | 48 ++- 5 files changed, 830 insertions(+), 16 deletions(-) create mode 100644 src/public/ding.mp3 diff --git a/package-lock.json b/package-lock.json index f23f9b5..8fd6f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,46 @@ "": { "dependencies": { "axios": "^1.13.2", - "dotenv": "^17.4.2" + "dotenv": "^17.4.2", + "express": "^5.2.1" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/asynckit": { @@ -26,6 +65,52 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -39,6 +124,22 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -51,6 +152,63 @@ "node": ">= 0.8" } }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -60,6 +218,15 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -86,6 +253,21 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -131,6 +313,110 @@ "node": ">= 0.4" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -167,6 +453,24 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -264,6 +568,63 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -273,6 +634,31 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -294,11 +680,403 @@ "node": ">= 0.6" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 3d2a034..f79061a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "axios": "^1.13.2", - "dotenv": "^17.4.2" + "dotenv": "^17.4.2", + "express": "^5.2.1" } } diff --git a/src/public/ding.mp3 b/src/public/ding.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f30e1bf5f5f7db3ecef2ed2c2496e144df24fb3d GIT binary patch literal 50501 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 WbK@9?mXfxLsDQALkm3K=@BaY*COZuP literal 0 HcmV?d00001 diff --git a/src/public/index.html b/src/public/index.html index f8af0ee..01c1bd5 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -302,6 +302,18 @@ footer { +
+

© 2026 Isaac Godin. All rights reserved.

+

Design and Credits: Isaac Godin

+
-- 2.45.2 From d15838d9704ad5bf2a8d0815008c44c1341b4131 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 27 Jul 2026 13:13:43 -0400 Subject: [PATCH 06/11] UI and a few server.js changes --- src/public/index.html | 39 ++++++++++++++++++++++----------------- src/server.js | 4 ++-- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/public/index.html b/src/public/index.html index 34c5347..d8a18dc 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -8,18 +8,22 @@