Compare commits

...

38 commits

Author SHA1 Message Date
3d8306b225 changing source of games 2024-09-21 13:25:42 +03:00
d3460f8871 logs added 2024-07-31 21:53:04 +03:00
15932a0c9d changing the cron to every min 2024-02-04 15:31:58 +02:00
3fef736821 changing the cron to be every day at 10 am 2024-02-04 13:40:17 +02:00
486f334b4a changing the logs 2024-02-04 13:38:35 +02:00
e133b49ee9 adding more headers 2024-02-04 13:10:12 +02:00
59552698e9 adding headers to each request to haifa 2024-02-04 13:05:18 +02:00
3ad063cbc2 remove log file from git 2024-02-04 13:01:06 +02:00
55b7b99e48 ran tsc locally 2024-02-04 13:00:14 +02:00
ea7e224c9e ran tsc locally 2024-02-04 12:56:26 +02:00
d5408c359b added try catch to haifa 2024-02-04 12:55:47 +02:00
265a182d8f added try catch to haifa 2024-02-04 12:54:32 +02:00
dbdf76554b remove https 2024-02-04 12:52:15 +02:00
daea0c9cfb invoke cron every min 2024-02-04 11:45:59 +02:00
54483b96b6 adding logs outputs of handeling events 2024-01-23 11:53:52 +02:00
522d30d7dd remove unused code 2023-09-10 13:02:43 +03:00
dc57073067 dockerfile - add TZ env var + title has changed to 2 teams playing 2023-09-10 09:51:08 +03:00
0362566c2a remove dist from repo 2023-09-10 09:50:05 +03:00
b07d434bf3 fixing posponded games 2023-08-21 09:35:44 +03:00
5588c999a7 finding dynamiclly the date and time from the headers 2023-08-15 15:01:11 +03:00
fd7e2377e1 testing 2023-08-13 11:44:41 +03:00
427423d3e8 remove the Value from the comment 2023-08-06 15:17:41 +03:00
87582b72ee build inside repo the dist 2023-08-06 11:25:52 +03:00
1f59ccfb2c cleaning 2023-08-06 10:48:24 +03:00
3aeb79f054 clean up 2023-08-06 00:35:07 +03:00
92a256c784 remove node_modules 2023-07-26 15:16:04 +03:00
fd4b483a6c remove node_modules 2023-07-26 15:15:51 +03:00
f95ed86012 remove node_modules 2023-07-26 15:15:38 +03:00
95b2f4283b done manually 2023-07-26 15:14:58 +03:00
43bb990df5 adding catch 2023-07-26 14:31:24 +03:00
a1ae8c98d1 testing 2023-07-26 14:14:10 +03:00
bbf1865f1a adding node_modules + dist 2023-07-26 14:02:08 +03:00
7675d627d8 adding node_modules + dist 2023-07-26 14:01:58 +03:00
18c3fcb06a start modify 2023-07-26 13:31:35 +03:00
a022002c36 start modify 2023-07-26 13:31:16 +03:00
d408a385bb google calendar enabled 2023-07-26 13:17:32 +03:00
Kfir Dayan
90bb926341 removed google api + changing game info 2023-04-16 14:53:59 +03:00
Kfir Dayan
0fea123354 removed google api + changing game info 2023-04-16 14:53:39 +03:00
17 changed files with 845 additions and 874 deletions

View file

@ -1,9 +1,11 @@
# Google
SERPAPI_KEY
# Google # Google
GOOGLE_CALENDAR_ID GOOGLE_CALENDAR_ID
GOOGLE_PROJECT_NUMBER GOOGLE_PROJECT_NUMBER
GOOGLE_CLIENT_EMAIL GOOGLE_CLIENT_EMAIL
GOOGLE_PRIVATE_KEY GOOGLE_PRIVATE_KEY
GOOGLE_KEY_ID
GOOGLE_CLIENT_ID GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET GOOGLE_CLIENT_SECRET

6
.gitignore vendored
View file

@ -2,8 +2,8 @@
node_modules node_modules
#javascript build files # #javascript build files #
public/**/* # public/**/*
dist/**/* dist/**/*
# Tmp files # # Tmp files #
tmp tmp
@ -19,3 +19,5 @@ push_dockerhub.sh
# keys # # keys #
keys/**/* keys/**/*
cron.log

View file

@ -1,28 +1,16 @@
FROM node:16 FROM node:16
# Create app directory ENV TZ=Asia/Jerusalem
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
RUN npm install -g typescript RUN npm install -g typescript
# If you are building your code for production
# RUN npm ci --omit=dev
# Bundle app source
COPY . . COPY . .
EXPOSE ${PORT} EXPOSE ${PORT}
# run npm build to create the dist folder
RUN tsc RUN tsc
# run npm start to start the server
CMD [ "npm", "start" ] CMD [ "npm", "start" ]

95
dist/GameSource.js vendored Normal file
View file

@ -0,0 +1,95 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv").config();
const axios_1 = __importDefault(require("axios"));
const moment_1 = __importDefault(require("moment"));
class GameSource {
async getGamesFromHaifa(logger) {
console.log("Trying to get games from Haifa...");
try {
// Get the current date and time in the required format
const currentDate = (0, moment_1.default)().format("DD/MM/YYYY HH:mm");
// Construct the filters object with the current date
const filters = {
date: {
startDate: currentDate,
endDate: "",
},
league: "",
session: "",
gamesDirection: "1",
};
// Encode the filters for the URL
const filtersParam = encodeURIComponent(JSON.stringify(filters));
// Construct the API URL with the encoded filters
const sourceUrl = `https://api.mhaifafc.com/api/content/games-lobby?filters=${filtersParam}&start=0&limit=20&sortDirection=ASC&app=web&lang=he`;
// Get the authorization token from environment variables
const authorizationToken = process.env.HAIFA_API_AUTH_TOKEN;
// Set up the request headers
const headers = {
Accept: "*/*",
"Accept-Language": "en-US,en;q=0.7",
Authorization: `Bearer ${authorizationToken}`,
"User-Agent": "Mozilla/5.0",
Origin: "https://www.mhaifafc.com",
Referer: "https://www.mhaifafc.com/",
};
// Make the API request
const response = await axios_1.default.get(sourceUrl, {
headers,
responseType: "json",
responseEncoding: "utf8", // Ensure UTF-8 encoding
});
// Extract the games data from the response
const gamesData = response.data.games.items;
const games = [];
// Loop through each game and construct the GoogleCalendarEvent objects
for (const game of gamesData) {
const gameDetails = game.gameDetails;
const gameTime = gameDetails.gameTime; // ISO string
const isFinalGameDate = gameDetails.isFinalGameDate;
const gameLocation = gameDetails.gameLocation;
// Skip games without a game time
if (!gameTime)
continue;
const hostTeam = game.hostTeam;
const guestTeam = game.guestTeam;
// Get team names
const hostTeamName = hostTeam.teamName;
const guestTeamName = guestTeam.teamName;
const summary = `${hostTeamName} vs. ${guestTeamName}`;
// Include a note if the game date is not final
let description = `${hostTeamName} vs. ${guestTeamName}`;
if (!isFinalGameDate) {
description += " (Date and time are subject to change)";
}
// Calculate start and end times
const startDateTime = (0, moment_1.default)(gameTime).toISOString();
const endDateTime = (0, moment_1.default)(gameTime).add(2, "hours").toISOString();
// Add the event to the games array
games.push({
summary: summary,
location: gameLocation,
description: description,
start: {
dateTime: startDateTime,
timeZone: "Asia/Jerusalem",
},
end: {
dateTime: endDateTime,
timeZone: "Asia/Jerusalem",
},
});
}
return games;
}
catch (error) {
console.error(error);
return [];
}
}
}
exports.default = GameSource;

88
dist/GoogleCalendar.js vendored Normal file
View file

@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const google_auth_library_1 = require("google-auth-library");
const googleapis_1 = require("googleapis");
require("dotenv").config();
const env = process.env;
class GoogleCalendar {
constructor() {
this.gamesMap = {};
this.clientSecret = env.GOOGLE_CLIENT_SECRET;
this.clientId = env.GOOGLE_CLIENT_ID;
this.calenderId = env.GOOGLE_CALENDAR_ID;
this.clientEmail = env.GOOGLE_CLIENT_EMAIL;
this.googlePrivateKey = env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n");
}
async init() {
console.log("INIT GOOGLE CALENDAR");
const jwtClient = await this.authorize();
this.calendar = googleapis_1.google.calendar({ version: "v3", auth: jwtClient });
}
async authorize() {
console.log("AUTHORIZE GOOGLE CALENDAR");
this.JWT_client = new google_auth_library_1.JWT({
email: this.clientEmail,
key: this.googlePrivateKey,
scopes: ["https://www.googleapis.com/auth/calendar"],
});
const { access_token } = await this.JWT_client.authorize();
this.token = access_token;
if (!this.token) {
throw new Error("Failed to connect to google calendar");
}
console.log("GOOGLE CALENDAR AUTHORIZED SUCCESSFULLY");
return this.JWT_client;
}
async updateNewEvent(upcomingEvents) {
// console.log(upcomingEvents)
setTimeout(async () => {
upcomingEvents.forEach(async (event) => {
console.log("UPDATE NEW EVENT", upcomingEvents);
const options = {
auth: this.JWT_client,
calendarId: this.calenderId,
resource: {
summary: event.summary,
location: event.location,
description: event.description,
start: {
dateTime: event.start.dateTime,
timeZone: "Asia/Jerusalem",
},
end: { dateTime: event.end.dateTime, timeZone: "Asia/Jerusalem" },
sendNotifications: true,
},
};
await this.calendar.events.insert(options, function (err, event) {
if (err) {
console.log("There was an error contacting the Calendar service: " + err);
return;
}
console.log(event.description + " created");
});
});
}, 3000);
}
async isDuplicateEvent(startTime, endTime, title) {
if (this.gamesMap[startTime]) {
console.log("duplicate event");
return true;
}
this.gamesMap[startTime] = true;
console.log("checking for duplicate event");
try {
const response = await this.calendar.events.list({
calendarId: this.calenderId,
timeMin: startTime,
timeMax: endTime,
q: title, // Search for events with the same title
});
return response.data.items.length > 0;
}
catch (error) {
console.error(error.message);
return false;
}
}
}
exports.default = GoogleCalendar;

68
dist/index.js vendored Normal file
View file

@ -0,0 +1,68 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const GameSource_1 = __importDefault(require("./GameSource"));
const GoogleCalendar_1 = __importDefault(require("./GoogleCalendar"));
const dotenv_1 = __importDefault(require("dotenv"));
const node_cron_1 = __importDefault(require("node-cron"));
const fs_1 = __importDefault(require("fs")); // Importing fs for logging
dotenv_1.default.config();
class App {
constructor() {
this.gameSource = new GameSource_1.default();
this.googleCalendar = new GoogleCalendar_1.default();
}
async startCronJob() {
this.writeLog('START Haifa Reminder'); // Log when the cron job starts
const newGamesAdded = [];
await this.googleCalendar.init();
try {
const games = await this.gameSource.getGamesFromHaifa(this.writeLog);
for (const game of games) {
const isDuplicateEvent = await this.googleCalendar.isDuplicateEvent(game.start.dateTime, game.end.dateTime, game.summary);
console.log(game);
if (!isDuplicateEvent) {
newGamesAdded.push(game);
console.log("Event does not exist");
await this.googleCalendar.updateNewEvent([game]);
}
else {
console.log("Event already exists");
}
}
if (newGamesAdded.length > 0) {
console.log("New games added:", newGamesAdded);
}
else {
console.log("No new games were Added!");
}
this.writeLog('Successfully ran project');
}
catch (error) {
this.writeLog("Error in cron job:" + error.message);
}
finally {
this.writeLog('END CRON JOB'); // Log when the cron job ends
}
}
writeLog(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp} - ${message}\n`;
// Write to log file synchronously
fs_1.default.appendFileSync('cron.log', logMessage);
console.log(logMessage); // Optional: also log to console
}
}
const app = new App();
node_cron_1.default.schedule('* * * * *', () => {
console.log('Running startCronJob at 10:00 AM Jerusalem time');
app.startCronJob().catch((error) => {
console.error("Error in scheduled cron job:", error.message);
app.writeLog(`ERROR: ${error.message}`); // Log any errors
});
}, {
scheduled: true,
timezone: "Asia/Jerusalem"
});

2
dist/types/index.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

915
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,11 @@
{ {
"dependencies": { "dependencies": {
"axios": "^1.3.4", "axios": "^1.3.4",
"cron": "^2.3.0",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "googleapis": "^122.0.0",
"ical": "^0.8.0",
"ics": "^3.1.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"node-cron": "^3.0.3",
"node-html-parser": "^6.1.5", "node-html-parser": "^6.1.5",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
@ -18,6 +17,7 @@
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/node": "^18.15.5", "@types/node": "^18.15.5",
"@types/node-cron": "^3.0.11",
"typescript": "^5.0.3" "typescript": "^5.0.3"
} }
} }

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
docker stop haifareminder docker stop haifareminder
docker rm haifareminder docker rm haifareminder
docker run -p 3000:3000 -d --name haifareminder docker.io/kfda89/haifareminder docker run -p 3000:3000 -d --restart=unless-stopped --name haifareminder docker.io/kfda89/haifareminder

View file

@ -1,63 +1,106 @@
require('dotenv').config(); require("dotenv").config();
import axios from "axios"; import axios from "axios";
import { GoogleCalendarEvent } from "./types"; import { GoogleCalendarEvent } from "./types";
import { parse } from 'node-html-parser'; import moment from "moment";
import moment from 'moment'; // require
// This calss will be the game source.
// search for upcomming games
export default class GameSource { export default class GameSource {
async getGamesFromHaifa(logger: Function): Promise<GoogleCalendarEvent[]> {
console.log("Trying to get games from Haifa...");
async getGamesFromHaifa(): Promise<GoogleCalendarEvent[]> { try {
const sourceUrl = `https://mhaifafc.com/games?lang=en`; // Get the current date and time in the required format
const result = await axios.get(sourceUrl); const currentDate = moment().format("DD/MM/YYYY HH:mm");
const parsedResult = parse(result.data.toString())
const gameBoxs = parsedResult.querySelectorAll(".game-box"); // Construct the filters object with the current date
const filters = {
date: {
startDate: currentDate,
endDate: "",
},
league: "",
session: "",
gamesDirection: "1",
};
// Encode the filters for the URL
const filtersParam = encodeURIComponent(JSON.stringify(filters));
// Construct the API URL with the encoded filters
const sourceUrl = `https://api.mhaifafc.com/api/content/games-lobby?filters=${filtersParam}&start=0&limit=20&sortDirection=ASC&app=web&lang=he`;
// Get the authorization token from environment variables
const authorizationToken = process.env.HAIFA_API_AUTH_TOKEN;
// Set up the request headers
const headers = {
Accept: "*/*",
"Accept-Language": "en-US,en;q=0.7",
Authorization: `Bearer ${authorizationToken}`,
"User-Agent": "Mozilla/5.0",
Origin: "https://www.mhaifafc.com",
Referer: "https://www.mhaifafc.com/",
};
// Make the API request
const response = await axios.get(sourceUrl, {
headers,
responseType: "json", // Ensure the response is parsed as JSON
responseEncoding: "utf8", // Ensure UTF-8 encoding
});
// Extract the games data from the response
const gamesData = response.data.games.items;
const games: GoogleCalendarEvent[] = []; const games: GoogleCalendarEvent[] = [];
for (let gameBox of gameBoxs) { // Loop through each game and construct the GoogleCalendarEvent objects
for (const game of gamesData) {
const gameDetails = game.gameDetails;
const gameTime = gameDetails.gameTime; // ISO string
const isFinalGameDate = gameDetails.isFinalGameDate;
const gameLocation = gameDetails.gameLocation;
const teamsPlaying = gameBox.querySelectorAll(".team-name").map((team: any) => team.text); // Skip games without a game time
const regex = /[\r\n\s]+/g; if (!gameTime) continue;
const gameHeader = gameBox.querySelector(".game-header").text.replace(regex, " ").trim();
const headerSplit = gameHeader.split(",");
// In data, if there is no time, it means it's the last game for the calender
const lastGameForCalender = headerSplit.length < 4;
if (lastGameForCalender) break;
const gameDate = headerSplit[2].trim(); const hostTeam = game.hostTeam;
const gameTime = headerSplit[3].trim(); const guestTeam = game.guestTeam;
const startOriginal = moment(gameDate + gameTime, "DD/MM/YYYYHH:mm").format("DD/MM/YYYY HH:mm"); // Get team names
const endOriginal = moment(gameDate + gameTime, "DD/MM/YYYYHH:mm").add(2, "hours").format("DD/MM/YYYY HH:mm"); const hostTeamName = hostTeam.teamName;
const guestTeamName = guestTeam.teamName;
const start = [moment(startOriginal, 'DD/MM/YYYYHH:mm').year(), moment(startOriginal, 'DD/MM/YYYYHH:mm').month() + 1, moment(startOriginal, 'DD/MM/YYYYHH:mm').date(), moment(startOriginal, 'DD/MM/YYYYHH:mm').hour(), moment(startOriginal, 'DD/MM/YYYYHH:mm').minute()]; const summary = `${hostTeamName} vs. ${guestTeamName}`;
const end = [moment(endOriginal, 'DD/MM/YYYYHH:mm').year(), moment(endOriginal, 'DD/MM/YYYYHH:mm').month() + 1, moment(endOriginal, 'DD/MM/YYYYHH:mm').date(), moment(endOriginal, 'DD/MM/YYYYHH:mm').hour(), moment(endOriginal, 'DD/MM/YYYYHH:mm').minute()]
// Include a note if the game date is not final
let description = `${hostTeamName} vs. ${guestTeamName}`;
if (!isFinalGameDate) {
description += " (Date and time are subject to change)";
}
// Calculate start and end times
const startDateTime = moment(gameTime).toISOString();
const endDateTime = moment(gameTime).add(2, "hours").toISOString();
// Add the event to the games array
games.push({ games.push({
summary: 'Maccabi Haifa F.C.', summary: summary,
location: "Sammy Ofer Stadium", location: gameLocation,
description: `${teamsPlaying[0]} vs. ${teamsPlaying[1]}`, description: description,
start: { start: {
dateTime: start, dateTime: startDateTime,
timeZone: 'Asia/Jerusalem' timeZone: "Asia/Jerusalem",
}, },
end: { end: {
dateTime: end, dateTime: endDateTime,
timeZone: 'Asia/Jerusalem' timeZone: "Asia/Jerusalem",
} },
}) });
} }
return games; return games;
} } catch (error) {
console.error(error);
getOpponentIndexByStadium(stadium: string) { return [];
if (stadium === "Sammy Ofer Stadium") {
return 1;
} else {
return 0;
} }
} }
} }

View file

@ -1,65 +0,0 @@
// import { JWT } from 'google-auth-library';
// import { google } from 'googleapis';
// import { GoogleCalendarEvent } from './types/index';
// require('dotenv').config();
// const env = process.env;
// export default class GoogleCalendar {
// clientSecret: string = env.GOOGLE_CLIENT_SECRET;
// clientId: string = env.GOOGLE_CLIENT_ID;
// calenderId: string = env.GOOGLE_CALENDAR_ID;
// calendar: any;
// clientEmail: string = env.GOOGLE_CLIENT_EMAIL;
// googlePrivateKey: string = env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n');
// token: any;
// JWT_client: JWT;
// async init() {
// console.log("INIT GOOGLE CALENDAR")
// const jwtClient = await this.authorize();
// this.calendar = google.calendar({ version: 'v3', auth: jwtClient });
// console.log("DONE INIT GOOGLE CALENDAR")
// }
// async authorize() {
// console.log("AUTHORIZE GOOGLE CALENDAR")
// this.JWT_client = new JWT({
// email: this.clientEmail,
// key: this.googlePrivateKey,
// scopes: [
// 'https://www.googleapis.com/auth/calendar',
// ]
// });
// const { access_token } = await this.JWT_client.authorize();
// this.token = access_token;
// if (!this.token) {
// throw new Error('Failed to connect to google calendar');
// }
// console.log("GOOGLE CALENDAR AUTHORIZED SUCCESSFULLY")
// return this.JWT_client;
// }
// async updateNewEvent(upcomingEvents: GoogleCalendarEvent[]) {
// setTimeout(async () => {
// upcomingEvents.forEach((event: GoogleCalendarEvent) => {
// console.log("UPDATE NEW EVENT", upcomingEvents)
// const options = {
// auth: this.JWT_client,
// calendarId: this.calenderId,
// resource: event,
// }
// this.calendar.events.insert(options, function (err: any, event: any) {
// if (err) {
// console.log('There was an error contacting the Calendar service: ' + err);
// return;
// }
// console.log(event.description + ' created');
// });
// })
// }, 3000)
// }
// }

97
src/GoogleCalendar.ts Normal file
View file

@ -0,0 +1,97 @@
import { JWT } from "google-auth-library";
import { google } from "googleapis";
import { GoogleCalendarEvent } from "./types/index";
require("dotenv").config();
const env = process.env;
export default class GoogleCalendar {
gamesMap: any = {};
clientSecret: string = env.GOOGLE_CLIENT_SECRET;
clientId: string = env.GOOGLE_CLIENT_ID;
calenderId: string = env.GOOGLE_CALENDAR_ID;
calendar: any;
clientEmail: string = env.GOOGLE_CLIENT_EMAIL;
googlePrivateKey: string = env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n");
token: any;
JWT_client: JWT;
async init() {
console.log("INIT GOOGLE CALENDAR");
const jwtClient = await this.authorize();
this.calendar = google.calendar({ version: "v3", auth: jwtClient });
}
async authorize() {
console.log("AUTHORIZE GOOGLE CALENDAR");
this.JWT_client = new JWT({
email: this.clientEmail,
key: this.googlePrivateKey,
scopes: ["https://www.googleapis.com/auth/calendar"],
});
const { access_token } = await this.JWT_client.authorize();
this.token = access_token;
if (!this.token) {
throw new Error("Failed to connect to google calendar");
}
console.log("GOOGLE CALENDAR AUTHORIZED SUCCESSFULLY");
return this.JWT_client;
}
async updateNewEvent(upcomingEvents: GoogleCalendarEvent[]) {
// console.log(upcomingEvents)
setTimeout(async () => {
upcomingEvents.forEach(async (event: GoogleCalendarEvent) => {
console.log("UPDATE NEW EVENT", upcomingEvents);
const options = {
auth: this.JWT_client,
calendarId: this.calenderId,
resource: {
summary: event.summary,
location: event.location,
description: event.description,
start: {
dateTime: event.start.dateTime,
timeZone: "Asia/Jerusalem",
},
end: { dateTime: event.end.dateTime, timeZone: "Asia/Jerusalem" },
sendNotifications: true,
},
};
await this.calendar.events.insert(
options,
function (err: any, event: any) {
if (err) {
console.log(
"There was an error contacting the Calendar service: " + err
);
return;
}
console.log(event.description + " created");
}
);
});
}, 3000);
}
async isDuplicateEvent(startTime: string, endTime: string, title: string) {
if (this.gamesMap[startTime]) {
console.log("duplicate event");
return true;
}
this.gamesMap[startTime] = true;
console.log("checking for duplicate event");
try {
const response = await this.calendar.events.list({
calendarId: this.calenderId,
timeMin: startTime,
timeMax: endTime,
q: title, // Search for events with the same title
});
return response.data.items.length > 0;
} catch (error) {
console.error(error.message);
return false;
}
}
}

View file

@ -1,48 +0,0 @@
import { GoogleCalendarEvent } from "./types";
import * as ics from 'ics';
const uuid = require('uuid').v4();
export default class Ics {
generateIcsOutputFromGames = (games: GoogleCalendarEvent[]) => {
let output = [];
games.forEach((game) => {
output.push(this.generateIcsOutputFromGame(game));
});
const { error, value } = ics.createEvents(output);
if (error) {
console.log(error);
return '';
}
return value;
}
generateIcsOutputFromGame = (game: GoogleCalendarEvent) => {
const { summary, location, description, start, end } = game;
const icsEvent: ics.EventAttributes = {
title: summary,
description,
location,
start: [start.dateTime[0], start.dateTime[1], start.dateTime[2], start.dateTime[3], start.dateTime[4]],
end: [end.dateTime[0], end.dateTime[1], end.dateTime[2], end.dateTime[3], end.dateTime[4]],
url: 'https://mhaifafc.com/',
status: 'CONFIRMED',
busyStatus: 'BUSY',
productId: 'https://mhaifafc.com/',
recurrenceRule: '',
attendees: [],
alarms: [],
categories: [],
organizer: { name: 'Maccabi Haifa F.C.', email: '' }
};
return icsEvent;
}
convertIcsToIcal = (icsEvents: string) => {
const icalEvents = icsEvents.replace(/BEGIN:VEVENT/g, 'BEGIN:VEVENT\r\nUID:' + uuid);
return icalEvents;
}
}

View file

@ -1,85 +1,74 @@
// import GoogleCalendar from './GoogleCalendar'; import GameSource from "./GameSource";
import GameSource from './GameSource'; import GoogleCalendar from "./GoogleCalendar";
import fs from 'fs'; import env from "dotenv";
import Ics from './Ics'; import cron from 'node-cron';
import express from 'express' import fs from 'fs'; // Importing fs for logging
import env from 'dotenv';
env.config(); env.config();
class App { class App {
// googleCalendar: GoogleCalendar;
gameSource: GameSource; gameSource: GameSource;
ics: Ics;
googleToken: string; googleToken: string;
googleCalendar: GoogleCalendar;
constructor() { constructor() {
// this.googleCalendar = new GoogleCalendar();
this.gameSource = new GameSource(); this.gameSource = new GameSource();
this.ics = new Ics(); this.googleCalendar = new GoogleCalendar();
} }
// async init() {
// console.log("INIT APP")
// await this.googleCalendar.init();
// }
// async getNewGamesAndUpdateCalendar() {
// console.log("GET NEW GAMES AND UPDATE CALENDAR")
// const games = await this.gameSource.getGamesFromHaifa();
// this.googleCalendar.updateNewEvent(games);
// }
async startCronJob() { async startCronJob() {
console.log("START CRON JOB") this.writeLog('START Haifa Reminder'); // Log when the cron job starts
const CronJob = require('cron').CronJob; const newGamesAdded = [];
const job = new CronJob( await this.googleCalendar.init();
"* * * * *", // every day at 10:00, try {
async () => { const games = await this.gameSource.getGamesFromHaifa(this.writeLog);
console.log("Staring a new job") for (const game of games) {
const outputFileLocation = 'public/maccabi-haifa-fc.ics'; const isDuplicateEvent = await this.googleCalendar.isDuplicateEvent(
console.log("Getting games from Haifa") game.start.dateTime,
const games = await app.gameSource.getGamesFromHaifa(); game.end.dateTime,
console.log("Generating ICS file") game.summary
const icsEvents = app.ics.generateIcsOutputFromGames(games);
console.log("Writing ICS file to " + outputFileLocation)
fs.writeFileSync(outputFileLocation, icsEvents);
console.log("Done Ics file")
console.log("converting ics file to ical file")
const outputIcalFileLocation = 'public/maccabi-haifa-fc.ical';
const icalEvents = app.ics.convertIcsToIcal(icsEvents);
console.log("Writing Ical file to " + outputIcalFileLocation)
fs.writeFileSync(outputIcalFileLocation, icalEvents);
},
null,
true,
'Asia/Jerusalem'
); );
console.log(game)
if (!isDuplicateEvent) {
newGamesAdded.push(game);
console.log("Event does not exist");
await this.googleCalendar.updateNewEvent([game]);
} else {
console.log("Event already exists");
}
} }
async startWebServer() { if (newGamesAdded.length > 0) {
const webServer = express(); console.log("New games added:", newGamesAdded);
webServer.use(express.static('public')) } else {
console.log("No new games were Added!");
}
this.writeLog('Successfully ran project');
} catch (error) {
this.writeLog("Error in cron job:" + error.message);
} finally {
this.writeLog('END CRON JOB'); // Log when the cron job ends
}
}
webServer.listen(process.env.PORT, () => { writeLog(message) {
console.log(`Calender app listening on port ${process.env.PORT}!`) const timestamp = new Date().toISOString();
}) const logMessage = `${timestamp} - ${message}\n`;
// Write to log file synchronously
webServer.use(function (req, res, next) { fs.appendFileSync('cron.log', logMessage);
res.status(404).send("This is not the page you are looking for...") console.log(logMessage); // Optional: also log to console
})
} }
} }
const app = new App(); const app = new App();
cron.schedule('* * * * *', () => {
app.startCronJob(); console.log('Running startCronJob at 10:00 AM Jerusalem time');
app.startWebServer(); app.startCronJob().catch((error) => {
console.error("Error in scheduled cron job:", error.message);
app.writeLog(`ERROR: ${error.message}`); // Log any errors
});
}, {
scheduled: true,
timezone: "Asia/Jerusalem"
});

View file

@ -3,11 +3,11 @@ export interface GoogleCalendarEvent {
location: string; location: string;
description: string; description: string;
start: { start: {
dateTime: number[]; dateTime: string;
timeZone: string; timeZone: string;
}; };
end: { end: {
dateTime: number[]; dateTime: string;
timeZone: string; timeZone: string;
}; };
} }

View file

@ -1,2 +1,3 @@
#!/bin/bash #!/bin/bash
docker run -p 3000:3000 -d --name haifareminder docker.io/kfda89/haifareminder docker run -p 3000:3000 -d --restart=unless-stopped --name haifareminder docker.io/kfda89/haifareminder