renamed folder "app" to "src"
This commit is contained in:
123
src/app.ts
Normal file
123
src/app.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { TEventType } from "./component/event/event.types";
|
||||
import { db } from "./sql";
|
||||
import { Event, type TEventEntityNew, type TGetEventsOptions } from "./component/event/events";
|
||||
import { sendNotification } from "./sendNotification";
|
||||
import { createPlaceholders, pad_l2 } from "./util";
|
||||
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
console.dir(argv)
|
||||
|
||||
// const TS_TODAY = new Date();
|
||||
|
||||
function getTsNow() {
|
||||
const now = new Date();
|
||||
const rtn = {
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth() + 1,
|
||||
day: now.getDate(),
|
||||
minute: now.getMinutes(),
|
||||
seconds: now.getSeconds()
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
async function main( ) {
|
||||
const TODAY = getTsNow();
|
||||
const events_currentMonth = await Event.fetch_events( TODAY.year, TODAY.month , -120 );
|
||||
const events_nextMonth = await Event.fetch_events( TODAY.year, TODAY.month + 1 , -120 );
|
||||
const events = [...events_currentMonth, ...events_nextMonth];
|
||||
// Write to JSON File Section START
|
||||
// const data = JSON.stringify(events, null, 2);
|
||||
// const TS = `${TS_TODAY.getFullYear()}-${TS_TODAY.getMonth() + 1}-${TS_TODAY.getDate()}_${TS_TODAY.getHours()}-${TS_TODAY.getMinutes()}-${TS_TODAY.getSeconds()}`;
|
||||
// await Bun.write(path.join(import.meta.dir, "output", `output_${TS}.json`), data );
|
||||
// Write to JSON File Section END
|
||||
|
||||
const allEventUids = events.map( event => { return event.uid; });
|
||||
const placeholders = createPlaceholders( allEventUids );
|
||||
const getAllRelevantEventsQuery = db.query(
|
||||
`SELECT * FROM events WHERE uid IN (${placeholders}); `
|
||||
).as(Event );
|
||||
const AllRelevantEvents = getAllRelevantEventsQuery.all(...allEventUids);
|
||||
|
||||
const eventsToInsert: TEventEntityNew[] = [];
|
||||
for ( const ev of events ) {
|
||||
const found = AllRelevantEvents.find(event => event.uid === ev.uid);
|
||||
if ( found ) {
|
||||
if (
|
||||
found.title != ev.title ||
|
||||
found.description != ev.description ||
|
||||
found.date_at != ev.date_at ||
|
||||
found.time_start != ev.time_start ||
|
||||
found.time_end != ev.time_end ||
|
||||
found.posted_by != ev.posted_by ||
|
||||
found.location != ev.location ||
|
||||
found.event_type != ev.event_type ||
|
||||
found.timezone != ev.timezone ||
|
||||
found.link != ev.link
|
||||
) {
|
||||
const newEventToInsert: TEventEntityNew = {... ev, notification: "changed"};
|
||||
eventsToInsert.push( newEventToInsert );
|
||||
}
|
||||
} else {
|
||||
const newEventToInsert: TEventEntityNew = {... ev, notification: "new"};
|
||||
eventsToInsert.push( newEventToInsert );
|
||||
}
|
||||
}
|
||||
|
||||
Event.insert( eventsToInsert, db);
|
||||
const options: TGetEventsOptions = {
|
||||
}
|
||||
if (argv.today) {
|
||||
options.date = {
|
||||
year: TODAY.year,
|
||||
month: TODAY.month,
|
||||
day: TODAY.day
|
||||
}
|
||||
} else {
|
||||
options.notification = ["new", "changed"]
|
||||
}
|
||||
const list_of_events = Event.get_events( options, db );
|
||||
for ( const ev of list_of_events ) {
|
||||
const body = [
|
||||
`Title: ${ev.title}`,
|
||||
`Location: ${ev.location}`,
|
||||
`Type: ${ TEventType[ ev.event_type ] }`,
|
||||
`Date: ${ev.date_at}`,
|
||||
`Time: ${ev.time_start}`,
|
||||
`By: ${ev.posted_by}`,
|
||||
`Link: ${ev.link}`,
|
||||
].join("\n");
|
||||
const notification_prefix = ( (event: Event) => {
|
||||
switch( event.notification) {
|
||||
case "new":
|
||||
return "New";
|
||||
case "changed":
|
||||
return "Changed";
|
||||
case "deleted":
|
||||
return "Deleted";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} ) ( ev );
|
||||
|
||||
const today_prefix = ( (ev: Event) => {
|
||||
const now = getTsNow();
|
||||
const [year, month, day] = ev.date_at.split("-")
|
||||
if (
|
||||
year == String(now.year) &&
|
||||
month == pad_l2( String(now.month) ) &&
|
||||
day == pad_l2( String( now.day ) )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})( ev );
|
||||
sendNotification(
|
||||
`${today_prefix ? "TODAY " : ""}${notification_prefix ? notification_prefix + ": " : ""} ${ev.title} (${ TEventType[ ev.event_type ] })`,
|
||||
`${body}`
|
||||
// `${ev.link || "https://77th-jsoc.com/#/events"}`
|
||||
);
|
||||
ev.set_notification("done", db);
|
||||
}
|
||||
};
|
||||
main();
|
||||
19
src/component/event/event.types.ts
Normal file
19
src/component/event/event.types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const TEventType = {
|
||||
"1": "Public Event",
|
||||
"2": "Private Mission",
|
||||
"3": "Private Meeting"
|
||||
} as const
|
||||
|
||||
export type TEvent = {
|
||||
uid: string,
|
||||
title: string,
|
||||
description: string,
|
||||
date_at: string,
|
||||
time_start: string,
|
||||
time_end: string,
|
||||
posted_by: string,
|
||||
location: string,
|
||||
event_type: keyof typeof TEventType,
|
||||
timezone: string,
|
||||
link: string
|
||||
};
|
||||
141
src/component/event/events.ts
Normal file
141
src/component/event/events.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import type { TEvent } from "./event.types";
|
||||
import { transformArray } from "../../util";
|
||||
|
||||
const BASE_URL = "https://77th-jsoc.com/service.php?action=get_events";
|
||||
|
||||
export type TGetEventsOptions = {
|
||||
notification?: TEventEntity["notification"][] | null,
|
||||
date?: {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number
|
||||
}
|
||||
}
|
||||
export type TEventEntity = TEvent & {
|
||||
event_uid: number
|
||||
notification: "new" | "changed" | "deleted" | "done"
|
||||
}
|
||||
|
||||
export type TEventEntityNew = Omit<TEventEntity, "event_uid">
|
||||
|
||||
export class Event implements TEventEntity {
|
||||
static table_name: "events"
|
||||
static createTable (db: Database): void {
|
||||
const query = db.query(`CREATE TABLE IF NOT EXISTS events (
|
||||
event_uid INTEGER PRIMARY KEY,
|
||||
uid TEXT NOT NULL UNIQUE,
|
||||
title TEXT NOT NULL,
|
||||
date_at DATETIME NOT NULL,
|
||||
time_start TEXT NOT NULL,
|
||||
time_end TEXT NOT NULL,
|
||||
posted_by TEXT NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
link TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
timezone TEXT NOT NULL,
|
||||
notification TEXT NOT NULL DEFAULT "new"
|
||||
);`);
|
||||
query.run();
|
||||
}
|
||||
|
||||
static insert ( events: TEventEntityNew[], db: Database ) {
|
||||
const insert = db.prepare("INSERT OR REPLACE INTO events (uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification) VALUES ($uid, $title, $date_at, $time_start, $time_end, $posted_by, $location, $event_type, $link, $description, $timezone, $notification)");
|
||||
const insertEvents = db.transaction(events => {
|
||||
for (const event of events) insert.run(event);
|
||||
return events.length;
|
||||
});
|
||||
|
||||
const transforedEventArray = transformArray( events );
|
||||
const count = insertEvents(transforedEventArray);
|
||||
|
||||
console.log(`Inserted ${count} events`);
|
||||
}
|
||||
|
||||
static async fetch_events( _year_: number, _month_: number, timezone: number): Promise<TEvent[]> {
|
||||
const url = `${BASE_URL}&year=${_year_}&month=${_month_}&timezone=${timezone}`
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
});
|
||||
const body = await response.json() as {events: TEvent[] };
|
||||
const events = body.events.sort( ( a, b ) => ( new Date(a.date_at) < new Date(b.date_at ) ) ? -1 : 1 );
|
||||
return events;
|
||||
}
|
||||
|
||||
static get_events (options: TGetEventsOptions, db: Database ) {
|
||||
const whereConditions: string[] = [];
|
||||
if ( options.notification ) {
|
||||
whereConditions.push( `notification IN ('${ options.notification.join("', '") }')` )
|
||||
}
|
||||
if (options.date) {
|
||||
whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`);
|
||||
}
|
||||
const where = ( () => {
|
||||
let str = "WHERE ";
|
||||
if ( whereConditions.length >= 1 ) {
|
||||
str += whereConditions.join(" OR ");
|
||||
}
|
||||
return str;
|
||||
})()
|
||||
const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event);
|
||||
return query.all();
|
||||
}
|
||||
|
||||
event_uid: number;
|
||||
uid: string;
|
||||
title: string;
|
||||
description: string;
|
||||
date_at: string;
|
||||
time_start: string;
|
||||
time_end: string;
|
||||
posted_by: string;
|
||||
location: string;
|
||||
event_type: TEventEntity["event_type"];
|
||||
timezone: string;
|
||||
link: string;
|
||||
notification: TEventEntity["notification"]
|
||||
|
||||
constructor(event_uid: number, uid: string, title: string, description: string, date_at: string, time_start: string, time_end: string, posted_by: string, location: string, event_type: TEventEntity["event_type"], timezone: string, link: string, notification: TEventEntity["notification"]) {
|
||||
this.event_uid = event_uid;
|
||||
this.uid = uid;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.date_at = date_at;
|
||||
this.time_start = time_start;
|
||||
this.time_end = time_end;
|
||||
this.posted_by = posted_by;
|
||||
this.location = location;
|
||||
this.event_type = event_type;
|
||||
this.timezone = timezone;
|
||||
this.link = link;
|
||||
this.notification = notification;
|
||||
}
|
||||
syncWithDb ( db: Database ) {
|
||||
const query = db.prepare( `SELECT * FROM ${Event.table_name} WHERE event_uid = $event_uid;`).as(Event);
|
||||
const entity = query.get({$event_uid: this.event_uid });
|
||||
if ( ! entity ) { throw new Error(`Could not find Event with event_uid ${this.event_uid} in DB!`); }
|
||||
this.uid = entity.uid;
|
||||
this.title = entity.title;
|
||||
this.description = entity.description;
|
||||
this.date_at = entity.date_at;
|
||||
this.time_start = entity.time_start;
|
||||
this.time_end = entity.time_end;
|
||||
this.posted_by = entity.posted_by;
|
||||
this.location = entity.location;
|
||||
this.event_type = entity.event_type;
|
||||
this.timezone = entity.timezone;
|
||||
this.link = entity.link;
|
||||
this.notification = entity.notification;
|
||||
return this;
|
||||
}
|
||||
|
||||
set_notification ( newValue: TEventEntity["notification"], db: Database ) {
|
||||
const query = db.prepare(
|
||||
`UPDATE events
|
||||
SET notification = $notification
|
||||
WHERE event_uid = $event_uid;`
|
||||
);
|
||||
query.get({$notification: newValue, $event_uid: this.event_uid });
|
||||
}
|
||||
}
|
||||
2
src/component/event/index.ts
Normal file
2
src/component/event/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./events"
|
||||
export * from "./event.types";
|
||||
37
src/notification.py
Normal file
37
src/notification.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv() # Load environment variables from .env file
|
||||
ntfy_username = os.getenv('ntfy_username')
|
||||
ntfy_password = os.getenv('ntfy_password')
|
||||
ntfy_host = os.getenv('ntfy_host')
|
||||
ntfy_topic = os.getenv('ntfy_topic')
|
||||
dc_webhook = os.getenv('dc_webhook')
|
||||
dc_botname = os.getenv('dc_botname')
|
||||
dc_avatar_url = os.getenv('dc_avatar_url')
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import apprise
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("--title")
|
||||
parser.add_argument("--body")
|
||||
parser.add_argument("--click")
|
||||
args = parser.parse_args()
|
||||
print(args)
|
||||
|
||||
apobj = apprise.Apprise()
|
||||
# config = apprise.AppriseConfig()
|
||||
# config.add('https://myserver:8080/path/to/config')
|
||||
if ntfy_host and ntfy_topic:
|
||||
ntfy_link = f"ntfys://{ntfy_username}:{ntfy_password}@{ntfy_host}/{ntfy_topic}"
|
||||
if args.click:
|
||||
ntfy_link = ntfy_link + "?click=" + args.click
|
||||
apobj.add(ntfy_link)
|
||||
if dc_webhook:
|
||||
apobj.add(f"discord://{dc_webhook}?avatar_url={dc_avatar_url}&botname={dc_botname}");
|
||||
|
||||
apobj.notify(
|
||||
body=args.body,
|
||||
title=args.title
|
||||
)
|
||||
14
src/sendNotification.ts
Normal file
14
src/sendNotification.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as Bun from "bun";
|
||||
|
||||
export function sendNotification(title: string, body: string, click?: string | null) {
|
||||
const command = [
|
||||
"python",
|
||||
"./app/notification.py",
|
||||
`--title=${title}`,
|
||||
`--body=${body}`,
|
||||
];
|
||||
if (click) {
|
||||
command.push(`--click=${click}`);
|
||||
}
|
||||
Bun.spawn(command);
|
||||
}
|
||||
13
src/sql.ts
Normal file
13
src/sql.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import * as path from "node:path";
|
||||
import { Event } from "./component/event";
|
||||
export const db_filename = "77th_eventntfy.db";
|
||||
export const db_filepath = path.join("data", "db", db_filename);
|
||||
console.log(db_filepath);
|
||||
// const db_file = Bun.file(db_filepath);
|
||||
|
||||
export const db = new Database(db_filepath);
|
||||
|
||||
export function init () {
|
||||
Event.createTable(db);
|
||||
}
|
||||
29
src/util.ts
Normal file
29
src/util.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const createPlaceholders = ( arr: any[] ) => {
|
||||
return arr.map(() => '?').join(', ');
|
||||
}
|
||||
|
||||
export type AddDollarPrefix<T> = {
|
||||
[K in keyof T as `$${string & K}`]: T[K];
|
||||
};
|
||||
|
||||
export function prefixKeysWithDollar<T extends Record<string, any>>(obj: T): AddDollarPrefix<T> {
|
||||
const result = {} as AddDollarPrefix<T>;
|
||||
|
||||
for (const key in obj) {
|
||||
const newKey = `$${key}` as keyof AddDollarPrefix<T>;
|
||||
result[newKey] = obj[key] as any;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function transformArray<T extends Record<string, any>>(arr: T[]): AddDollarPrefix<T>[] {
|
||||
return arr.map(prefixKeysWithDollar);
|
||||
}
|
||||
|
||||
export function pad_l2 ( _thing: string | number ): string {
|
||||
if ( typeof _thing == "number" ) {
|
||||
_thing = JSON.stringify(_thing);
|
||||
};
|
||||
return _thing.padStart(2, "0");
|
||||
}
|
||||
Reference in New Issue
Block a user