summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example2
-rw-r--r--aggietimed.service11
-rwxr-xr-xcli.js4
-rw-r--r--package.json6
-rw-r--r--src/actions.js4
-rw-r--r--src/aggietime.js114
-rw-r--r--src/constants.js5
-rw-r--r--src/main.js42
8 files changed, 161 insertions, 27 deletions
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();