From 312af9bc10d9cf2982e66153fbc1cd949ad1f169 Mon Sep 17 00:00:00 2001 From: Lizzy Hunt Date: Thu, 16 Feb 2023 13:55:40 -0700 Subject: [PATCH] Add flag to connect over socket and retrieve action response, simple systemd service, and more actions --- .env.example | 2 + aggietimed.service | 11 +++++ cli.js | 4 ++ package.json | 6 +++ src/actions.js | 4 ++ src/aggietime.js | 116 ++++++++++++++++++++++++++++++++++++--------- src/constants.js | 5 +- src/main.js | 42 ++++++++++++++-- 8 files changed, 162 insertions(+), 28 deletions(-) create mode 100644 .env.example create mode 100644 aggietimed.service create mode 100755 cli.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d368299 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +A_NUMBER=A12345671 +PASSWORD= diff --git a/aggietimed.service b/aggietimed.service new file mode 100644 index 0000000..2cbaef1 --- /dev/null +++ b/aggietimed.service @@ -0,0 +1,11 @@ +[Unit] +Description=aggietimed +After=network.target + +[Service] +Type=simple +EnvironmentFile=/home/lizzy/work/simple_scripts/aggietime_cli/.env +ExecStart=/usr/bin/aggietimed -d -s /tmp/aggietimed.sock + +[Install] +WantedBy=multi-user.target diff --git a/cli.js b/cli.js new file mode 100755 index 0000000..289ecfa --- /dev/null +++ b/cli.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +import main from './src/main.js'; +main(); diff --git a/package.json b/package.json index 9524710..fe79471 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "name": "aggietimed", + "version": "0.0.1", + "author": "Lizzy Hunt (@simponic)", "dependencies": { "argparse": "^2.0.1", "axios": "^1.3.3", @@ -11,5 +14,8 @@ "type": "module", "scripts": { "start": "node src/main.js" + }, + "bin": { + "aggietimed": "cli.js" } } diff --git a/src/actions.js b/src/actions.js index 37b64f4..af22c3a 100644 --- a/src/actions.js +++ b/src/actions.js @@ -2,6 +2,10 @@ import * as aggietime from "./aggietime.js"; const ACTIONS = { "clock-in": aggietime.clock_in, + "clock-out": aggietime.clock_out, + "current-shift": aggietime.current_shift, + "current-user": aggietime.get_user_info, + "status-line": aggietime.get_status_line, }; export const do_action = async (body) => { diff --git a/src/aggietime.js b/src/aggietime.js index 793481c..e039766 100644 --- a/src/aggietime.js +++ b/src/aggietime.js @@ -4,12 +4,19 @@ import { USER_PATH, USER_CACHE_EXP_SEC, CLOCKIN_PATH, + CLOCKOUT_PATH, + OPEN_SHIFT_PATH, + OPEN_SHIFT_EXP_SEC, } from "./constants.js"; import { client } from "./axios_client.js"; import expireCache from "expire-cache"; +const aggietime = client.create({ + baseURL: AGGIETIME_URI, +}); + const replace_path_args = (path, map) => path.replaceAll(/:([a-zA-Z0-9_]+)/g, (_, key) => map[key]); @@ -27,18 +34,16 @@ const get_user_position_or_specified = async (position) => { export const get_user_info = async () => { if (!expireCache.get("user")) { - const user = await client - .get(`${AGGIETIME_URI}/${USER_PATH}`) - .then(({ data, config }) => { - const csrf_token = config.jar - .toJSON() - .cookies.find( - ({ domain, key }) => - domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN" - ).value; - expireCache.set("aggietime-csrf", csrf_token); - return data; - }); + const user = await aggietime.get(USER_PATH).then(({ data, config }) => { + const csrf_token = config.jar + .toJSON() + .cookies.find( + ({ domain, key }) => + domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN" + ).value; + expireCache.set("aggietime-csrf", csrf_token); + return data; + }); expireCache.set("user", user, USER_CACHE_EXP_SEC); } @@ -48,15 +53,82 @@ export const get_user_info = async () => { export const clock_in = async ({ position } = {}) => { position = await get_user_position_or_specified(position); - return await client.post( - `${AGGIETIME_URI}/${replace_path_args(CLOCKIN_PATH, { position })}`, - { - comment: "", - }, - { - headers: { - "X-XSRF-TOKEN": expireCache.get("aggietime-csrf"), + return await aggietime + .post( + replace_path_args(CLOCKIN_PATH, { position }), + { + comment: "", }, - } - ); + { + headers: { + "X-XSRF-TOKEN": expireCache.get("aggietime-csrf"), + }, + } + ) + .then(({ data }) => { + expireCache.remove("status_line"); + return data; + }); +}; + +export const clock_out = async ({ position } = {}) => { + position = await get_user_position_or_specified(position); + + return await aggietime + .post( + replace_path_args(CLOCKOUT_PATH, { position }), + { + comment: "", + }, + { + headers: { + "X-XSRF-TOKEN": expireCache.get("aggietime-csrf"), + }, + } + ) + .then(({ data }) => { + expireCache.remove("status_line"); + return data; + }); +}; + +export const current_shift = async () => { + const req_path = replace_path_args(OPEN_SHIFT_PATH, await get_user_info()); + const { + request: { + res: { responseUrl: response_url }, + }, + data, + } = await aggietime.get(req_path); + + if (`${AGGIETIME_URI}/${req_path}` != response_url) { + // IMO a very weird decision - when there is no open shift the api redirects + // home instead of sending back a 404 or something actually *reasonable* :3 + return null; + } + + return data; +}; + +export const get_status_line = async () => { + if (!expireCache.get("status_line")) { + const { anumber } = await get_user_info(); + const shift = await current_shift(); + let status_line = "No Shift"; + + if (shift && shift?.start) { + const start = new Date(shift?.start); + status_line = + ((new Date().getTime() - start.getTime()) / (1000 * 60 * 60)).toFixed( + 2 + ) + " hours"; + } + + expireCache.set( + "status_line", + `${anumber} - ${status_line}`, + OPEN_SHIFT_EXP_SEC + ); + } + return { status: expireCache.get("status_line") }; }; diff --git a/src/constants.js b/src/constants.js index 1044d40..1f8a8e0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -3,11 +3,14 @@ export const KILL_SIGNALS = ["SIGINT", "SIGTERM", "SIGQUIT"]; export const AGGIETIME_DOMAIN = "aggietimeultra.usu.edu"; export const AGGIETIME_URI = `https://${AGGIETIME_DOMAIN}`; +export const REFRESH_JWT_MS = 5 * 1000 * 60; export const LOGIN_PATH = "api/v1/auth/login"; export const LOGOUT_PATH = "api/v1/auth/logout"; export const CLOCKIN_PATH = "api/v1/positions/:position/clock_in"; +export const CLOCKOUT_PATH = "api/v1/positions/:position/clock_out"; export const USER_PATH = "api/v1/auth/get_user_info"; -export const REFRESH_JWT_MS = 5 * 1000 * 60; +export const OPEN_SHIFT_PATH = "api/v1/users/:anumber/open_shift"; +export const OPEN_SHIFT_EXP_SEC = 60; export const EXECUTION_SELECTOR = "input[type=hidden][name=execution]"; export const DUO_IFRAME_SELECTOR = "#duo_iframe"; diff --git a/src/main.js b/src/main.js index 099e4af..bc81f15 100644 --- a/src/main.js +++ b/src/main.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + import { DEFAULT_SOCKET_PATH, KILL_SIGNALS, @@ -10,7 +12,7 @@ import * as net from "net"; import * as dotenv from "dotenv"; import * as fs from "fs"; -const main = async () => { +export default async () => { dotenv.config(); const args = build_args(); @@ -20,16 +22,35 @@ const main = async () => { } catch { fs.unlinkSync(args.socket_path); } + } else if (args.action) { + if (fs.existsSync(args.socket_path)) { + run_action(args.socket_path, args.action); + } else { + console.error(`ERR: No such socket '${args.socket_path}'`); + } } }; +const run_action = (socket_path, action) => { + const connection = net.connect(socket_path); + connection.on("data", (data) => { + if (Buffer.isBuffer(data)) { + console.log(data.toString().trim()); + } else { + console.log(data.trim()); + } + connection.end(); + }); + connection.write(JSON.stringify({ action })); +}; + const build_args = () => { const parser = new argparse.ArgumentParser({ description: "AggieTime CLI" }); parser.add_argument("-d", "--daemon", { help: "Start server as a process blocking daemon", action: argparse.BooleanOptionalAction, - default: true, + default: false, }); parser.add_argument("-s", "--socket_path", { @@ -37,6 +58,10 @@ const build_args = () => { help: `Set server socket path, defaults to ${DEFAULT_SOCKET_PATH}`, }); + parser.add_argument("-a", "--action", { + help: `Ignored when daemon flag is set. Returns the value of action (see actions.js) when sent over the socket.`, + }); + return parser.parse_args(); }; @@ -76,7 +101,16 @@ specify another socket path with --socket_path` return; } - actions.do_action(body); + actions + .do_action(body) + .then((resp) => { + client.write(JSON.stringify(resp) + "\r\n"); + }) + .catch((e) => { + console.error(e); + + client.write(JSON.stringify({ err: true }) + "\r\n"); + }); }); }); @@ -90,5 +124,3 @@ specify another socket path with --socket_path` process.on(signal, () => kill_server(unix_server, socket_path)) ); }; - -main();