Compare commits
74 commits
insert_eve
...
master
Author | SHA1 | Date | |
---|---|---|---|
3d8306b225 | |||
d3460f8871 | |||
15932a0c9d | |||
3fef736821 | |||
486f334b4a | |||
e133b49ee9 | |||
59552698e9 | |||
3ad063cbc2 | |||
55b7b99e48 | |||
ea7e224c9e | |||
d5408c359b | |||
265a182d8f | |||
dbdf76554b | |||
daea0c9cfb | |||
54483b96b6 | |||
522d30d7dd | |||
dc57073067 | |||
0362566c2a | |||
b07d434bf3 | |||
5588c999a7 | |||
fd7e2377e1 | |||
427423d3e8 | |||
87582b72ee | |||
1f59ccfb2c | |||
3aeb79f054 | |||
92a256c784 | |||
fd4b483a6c | |||
f95ed86012 | |||
95b2f4283b | |||
43bb990df5 | |||
a1ae8c98d1 | |||
bbf1865f1a | |||
7675d627d8 | |||
18c3fcb06a | |||
a022002c36 | |||
d408a385bb | |||
|
90bb926341 | ||
|
0fea123354 | ||
|
d512ca8197 | ||
|
9547cedf4b | ||
|
b6455824e6 | ||
|
b90d9524ff | ||
|
54aec5450f | ||
|
332f363efc | ||
|
52c8516a1e | ||
|
3a7803ef2b | ||
|
7859110d97 | ||
|
cfc2d3bc9a | ||
8b04c510a7 | |||
|
09f43fefe6 | ||
|
377f641d49 | ||
|
5e0e203fa3 | ||
|
831dfaaea0 | ||
|
4360499dd0 | ||
|
d6d682a4b0 | ||
|
75c43c3443 | ||
|
a1b1c326f8 | ||
|
a1d8757453 | ||
|
b8d0a40315 | ||
|
0b0a5efe5c | ||
|
cc5ab4227a | ||
|
67babeac15 | ||
|
463573d5a6 | ||
|
8c764da185 | ||
|
8770451ebc | ||
|
647d746732 | ||
|
4051a50387 | ||
|
e7efa713eb | ||
|
2daf19e30a | ||
|
f74b2294d0 | ||
|
1a9ff74063 | ||
|
654d9d0655 | ||
ae8df2b7ec | |||
|
f42ce1e319 |
18 changed files with 835 additions and 127 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
tmp
|
12
.env.example
12
.env.example
|
@ -1 +1,11 @@
|
||||||
SERPAPI_KEY={NEED_TO_HAVE_AN_ACCOUNT_WITH_SERPAPI_KEY}
|
# Google
|
||||||
|
SERPAPI_KEY
|
||||||
|
|
||||||
|
# Google
|
||||||
|
GOOGLE_CALENDAR_ID
|
||||||
|
GOOGLE_PROJECT_NUMBER
|
||||||
|
GOOGLE_CLIENT_EMAIL
|
||||||
|
GOOGLE_PRIVATE_KEY
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID
|
||||||
|
GOOGLE_CLIENT_SECRET
|
||||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -2,7 +2,8 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
#javascript build files #
|
#javascript build files #
|
||||||
public
|
# public/**/*
|
||||||
|
dist/**/*
|
||||||
|
|
||||||
# Tmp files #
|
# Tmp files #
|
||||||
tmp
|
tmp
|
||||||
|
@ -11,3 +12,12 @@ tmp
|
||||||
## ENV Vars ##
|
## ENV Vars ##
|
||||||
.env
|
.env
|
||||||
config/client_google_auth.json
|
config/client_google_auth.json
|
||||||
|
|
||||||
|
## Docker ##
|
||||||
|
build_image.sh
|
||||||
|
push_dockerhub.sh
|
||||||
|
|
||||||
|
# keys #
|
||||||
|
keys/**/*
|
||||||
|
|
||||||
|
cron.log
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
FROM node:16
|
||||||
|
|
||||||
|
ENV TZ=Asia/Jerusalem
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
RUN npm install -g typescript
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE ${PORT}
|
||||||
|
RUN tsc
|
||||||
|
|
||||||
|
CMD [ "npm", "start" ]
|
||||||
|
|
95
dist/GameSource.js
vendored
Normal file
95
dist/GameSource.js
vendored
Normal 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
88
dist/GoogleCalendar.js
vendored
Normal 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
68
dist/index.js
vendored
Normal 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
2
dist/types/index.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
300
package-lock.json
generated
300
package-lock.json
generated
|
@ -7,18 +7,102 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"googleapis": "^113.0.0"
|
"googleapis": "^122.0.0",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
|
"node-html-parser": "^6.1.5",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.15.5"
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/node": "^18.15.5",
|
||||||
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"typescript": "^5.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/body-parser": {
|
||||||
|
"version": "1.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
|
||||||
|
"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/connect": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/connect": {
|
||||||
|
"version": "3.4.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||||
|
"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express": {
|
||||||
|
"version": "4.17.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
|
||||||
|
"integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/body-parser": "*",
|
||||||
|
"@types/express-serve-static-core": "^4.17.33",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/serve-static": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/express-serve-static-core": {
|
||||||
|
"version": "4.17.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz",
|
||||||
|
"integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/qs": "*",
|
||||||
|
"@types/range-parser": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/mime": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.15.5",
|
"version": "18.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz",
|
||||||
"integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==",
|
"integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-cron": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/qs": {
|
||||||
|
"version": "6.9.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||||
|
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/range-parser": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/serve-static": {
|
||||||
|
"version": "1.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz",
|
||||||
|
"integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mime": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
@ -80,6 +164,11 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||||
|
},
|
||||||
"node_modules/buffer-equal-constant-time": {
|
"node_modules/buffer-equal-constant-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
@ -108,6 +197,32 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -132,6 +247,57 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.0.3",
|
"version": "16.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
|
@ -148,6 +314,17 @@
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/extend": {
|
"node_modules/extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
@ -196,23 +373,23 @@
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
"node_modules/gaxios": {
|
"node_modules/gaxios": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
|
||||||
"integrity": "sha512-aezGIjb+/VfsJtIcHGcBSerNEDdfdHeMros+RbYbGpmonKWQCOVOes0LVZhn1lDtIgq55qq0HaxymIoae3Fl/A==",
|
"integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"extend": "^3.0.2",
|
"extend": "^3.0.2",
|
||||||
"https-proxy-agent": "^5.0.0",
|
"https-proxy-agent": "^5.0.0",
|
||||||
"is-stream": "^2.0.0",
|
"is-stream": "^2.0.0",
|
||||||
"node-fetch": "^2.6.7"
|
"node-fetch": "^2.6.9"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gcp-metadata": {
|
"node_modules/gcp-metadata": {
|
||||||
"version": "5.2.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||||
"integrity": "sha512-aFhhvvNycky2QyhG+dcfEdHBF0FRbYcf39s6WNHUDysKSrbJ5vuFbjydxBcmewtXeV248GP8dWT3ByPNxsyHCw==",
|
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gaxios": "^5.0.0",
|
"gaxios": "^5.0.0",
|
||||||
"json-bigint": "^1.0.0"
|
"json-bigint": "^1.0.0"
|
||||||
|
@ -235,16 +412,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/google-auth-library": {
|
"node_modules/google-auth-library": {
|
||||||
"version": "8.7.0",
|
"version": "8.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz",
|
||||||
"integrity": "sha512-1M0NG5VDIvJZEnstHbRdckLZESoJwguinwN8Dhae0j2ZKIQFIV63zxm6Fo6nM4xkgqUr2bbMtV5Dgo+Hy6oo0Q==",
|
"integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arrify": "^2.0.0",
|
"arrify": "^2.0.0",
|
||||||
"base64-js": "^1.3.0",
|
"base64-js": "^1.3.0",
|
||||||
"ecdsa-sig-formatter": "^1.0.11",
|
"ecdsa-sig-formatter": "^1.0.11",
|
||||||
"fast-text-encoding": "^1.0.0",
|
"fast-text-encoding": "^1.0.0",
|
||||||
"gaxios": "^5.0.0",
|
"gaxios": "^5.0.0",
|
||||||
"gcp-metadata": "^5.0.0",
|
"gcp-metadata": "^5.3.0",
|
||||||
"gtoken": "^6.1.0",
|
"gtoken": "^6.1.0",
|
||||||
"jws": "^4.0.0",
|
"jws": "^4.0.0",
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
|
@ -268,9 +445,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/googleapis": {
|
"node_modules/googleapis": {
|
||||||
"version": "113.0.0",
|
"version": "122.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-113.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-122.0.0.tgz",
|
||||||
"integrity": "sha512-gsknEobaFuS+ACraCL3w22pAqeg1HselNhiSjcF5p2d2t2HB3QchbRDrFCfZ0wc4gKLYUBdmGzulCmRRL5qNXw==",
|
"integrity": "sha512-n8Gt7j9LzSkhQEGPOrcLBKxllTvW/0v6oILuwszL/zqgelNsGJYXVqPJllgJJ6RM7maJ6T35UBeYqI6GQ/IlJg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"google-auth-library": "^8.0.2",
|
"google-auth-library": "^8.0.2",
|
||||||
"googleapis-common": "^6.0.0"
|
"googleapis-common": "^6.0.0"
|
||||||
|
@ -330,6 +507,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/he": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
|
"bin": {
|
||||||
|
"he": "bin/he"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
@ -410,15 +595,53 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
|
||||||
|
"integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-cron/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.12",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
|
||||||
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
|
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": "^5.0.0"
|
"whatwg-url": "^5.0.0"
|
||||||
},
|
},
|
||||||
|
@ -442,6 +665,26 @@
|
||||||
"node": ">= 6.13.0"
|
"node": ">= 6.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-html-parser": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"he": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.12.3",
|
"version": "1.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
|
@ -456,9 +699,9 @@
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.11.1",
|
"version": "6.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.4"
|
||||||
},
|
},
|
||||||
|
@ -506,6 +749,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/url-template": {
|
"node_modules/url-template": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
|
||||||
|
|
16
package.json
16
package.json
|
@ -2,12 +2,22 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"googleapis": "^113.0.0"
|
"googleapis": "^122.0.0",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
|
"node-html-parser": "^6.1.5",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --watch insted --ignore tmp/ public/index.js"
|
"dev": "nodemon dist/index.js",
|
||||||
|
"build": "npx tsc",
|
||||||
|
"start": "node dist/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.15.5"
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/node": "^18.15.5",
|
||||||
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"typescript": "^5.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
restart.sh
Normal file
4
restart.sh
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
docker stop haifareminder
|
||||||
|
docker rm haifareminder
|
||||||
|
docker run -p 3000:3000 -d --restart=unless-stopped --name haifareminder docker.io/kfda89/haifareminder
|
|
@ -1,14 +1,106 @@
|
||||||
require('dotenv').config();
|
require("dotenv").config();
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
// This calss will be the game source.
|
import { GoogleCalendarEvent } from "./types";
|
||||||
// search for upcomming games
|
import moment from "moment";
|
||||||
|
|
||||||
export default class GameSource {
|
export default class GameSource {
|
||||||
constructor() {}
|
async getGamesFromHaifa(logger: Function): Promise<GoogleCalendarEvent[]> {
|
||||||
|
console.log("Trying to get games from Haifa...");
|
||||||
|
|
||||||
async getGames() {
|
try {
|
||||||
const sourceUrl = `https://serpapi.com/search.json?q=maccabi+haifa+next+games&api_key=${process.env.SERPAPI_KEY}&location=austin,+texas,+united+states`;
|
// 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");
|
||||||
return result;
|
|
||||||
|
// 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[] = [];
|
||||||
|
|
||||||
|
// 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 = moment(gameTime).toISOString();
|
||||||
|
const endDateTime = moment(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 [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,59 +1,97 @@
|
||||||
import { JWT } from 'google-auth-library';
|
import { JWT } from "google-auth-library";
|
||||||
import { google } from 'googleapis';
|
import { google } from "googleapis";
|
||||||
import { GoogleCalendarEvent } from './types/index';
|
import { GoogleCalendarEvent } from "./types/index";
|
||||||
|
|
||||||
require('dotenv').config();
|
require("dotenv").config();
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
|
|
||||||
|
|
||||||
export default class GoogleCalendar {
|
export default class GoogleCalendar {
|
||||||
client_secret: string;
|
gamesMap: any = {};
|
||||||
client_id: string;
|
clientSecret: string = env.GOOGLE_CLIENT_SECRET;
|
||||||
calender_id: string;
|
clientId: string = env.GOOGLE_CLIENT_ID;
|
||||||
|
calenderId: string = env.GOOGLE_CALENDAR_ID;
|
||||||
calendar: any;
|
calendar: any;
|
||||||
clientEmail: string;
|
clientEmail: string = env.GOOGLE_CLIENT_EMAIL;
|
||||||
googlePrivateKey: string;
|
googlePrivateKey: string = env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n");
|
||||||
token: any;
|
token: any;
|
||||||
|
JWT_client: JWT;
|
||||||
constructor() {
|
|
||||||
this.client_secret = env.GOOGLE_CLIENT_SECRET;
|
|
||||||
this.client_id = env.GOOGLE_CLIENT_ID;
|
|
||||||
this.calender_id = env.GOOGLE_CALENDAR_ID;
|
|
||||||
this.clientEmail = env.GOOGLE_CLIENT_EMAIL
|
|
||||||
this.googlePrivateKey = env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n');
|
|
||||||
// this.redirect_uris = env.GOOGLE_REDIRECT_URIS;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
console.log("INIT GOOGLE CALENDAR");
|
||||||
const jwtClient = await this.authorize();
|
const jwtClient = await this.authorize();
|
||||||
this.calendar = google.calendar({ version: 'v3', auth: jwtClient });
|
this.calendar = google.calendar({ version: "v3", auth: jwtClient });
|
||||||
}
|
}
|
||||||
|
|
||||||
async authorize() {
|
async authorize() {
|
||||||
const client = new JWT({
|
console.log("AUTHORIZE GOOGLE CALENDAR");
|
||||||
|
this.JWT_client = new JWT({
|
||||||
email: this.clientEmail,
|
email: this.clientEmail,
|
||||||
key: this.googlePrivateKey,
|
key: this.googlePrivateKey,
|
||||||
scopes: 'https://www.googleapis.com/auth/calendar.events'
|
scopes: ["https://www.googleapis.com/auth/calendar"],
|
||||||
});
|
});
|
||||||
const { access_token } = await client.authorize();
|
const { access_token } = await this.JWT_client.authorize();
|
||||||
this.token = access_token;
|
this.token = access_token;
|
||||||
if(!this.token) {
|
if (!this.token) {
|
||||||
throw new Error('Failed to connect to google calendar');
|
throw new Error("Failed to connect to google calendar");
|
||||||
}
|
}
|
||||||
return client;
|
console.log("GOOGLE CALENDAR AUTHORIZED SUCCESSFULLY");
|
||||||
|
return this.JWT_client;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNewEvent(upcomingEvents: GoogleCalendarEvent[]) {
|
async updateNewEvent(upcomingEvents: GoogleCalendarEvent[]) {
|
||||||
this.calendar.event.insert({
|
// console.log(upcomingEvents)
|
||||||
auth: this.token,
|
setTimeout(async () => {
|
||||||
calendarId: this.calender_id,
|
upcomingEvents.forEach(async (event: GoogleCalendarEvent) => {
|
||||||
resource: upcomingEvents[0],
|
console.log("UPDATE NEW EVENT", upcomingEvents);
|
||||||
}, function (err: any, event: any) {
|
const options = {
|
||||||
if (err) {
|
auth: this.JWT_client,
|
||||||
console.log('There was an error contacting the Calendar service: ' + err);
|
calendarId: this.calenderId,
|
||||||
return;
|
resource: {
|
||||||
}
|
summary: event.summary,
|
||||||
console.log('Event created: %s', event.htmlLink);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
107
src/index.ts
107
src/index.ts
|
@ -1,63 +1,74 @@
|
||||||
import GoogleCalendar from './GoogleCalendar';
|
import GameSource from "./GameSource";
|
||||||
import GameSource from './GameSource';
|
import GoogleCalendar from "./GoogleCalendar";
|
||||||
import { GoogleCalendarEvent } from './types/index';
|
import env from "dotenv";
|
||||||
|
import cron from 'node-cron';
|
||||||
|
import fs from 'fs'; // Importing fs for logging
|
||||||
// crete App class
|
|
||||||
// app class will be the main class of our application
|
|
||||||
// it will be collecting macabi haifa events from GameSource class
|
|
||||||
// it will be responsible for update GameSource events in google calendar
|
|
||||||
// it will be responsible for update GameSource events in local file
|
|
||||||
|
|
||||||
|
|
||||||
|
env.config();
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
gameSource: GameSource;
|
||||||
|
googleToken: string;
|
||||||
googleCalendar: GoogleCalendar;
|
googleCalendar: GoogleCalendar;
|
||||||
gameSource: any;
|
|
||||||
googleToken: any;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.googleCalendar = new GoogleCalendar();
|
|
||||||
this.gameSource = new GameSource();
|
this.gameSource = new GameSource();
|
||||||
|
this.googleCalendar = new GoogleCalendar();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async startCronJob() {
|
||||||
this.googleToken = this.googleCalendar.init();
|
this.writeLog('START Haifa Reminder'); // Log when the cron job starts
|
||||||
}
|
const newGamesAdded = [];
|
||||||
|
await this.googleCalendar.init();
|
||||||
getNewGamesAndUpdateCalendar() {
|
try {
|
||||||
const upcomingEvents: GoogleCalendarEvent[] = [];
|
const games = await this.gameSource.getGamesFromHaifa(this.writeLog);
|
||||||
this.gameSource.getGames().then((games: any) => {
|
for (const game of games) {
|
||||||
const rootGames = games.data.sports_results;
|
const isDuplicateEvent = await this.googleCalendar.isDuplicateEvent(
|
||||||
upcomingEvents.push({
|
game.start.dateTime,
|
||||||
summary: rootGames.title,
|
game.end.dateTime,
|
||||||
location: rootGames.game_spotlight.stadium,
|
game.summary
|
||||||
description: "Haifa vs. " + rootGames.game_spotlight.teams[this.getHaifaIndex(rootGames.game_spotlight.teams)].name,
|
);
|
||||||
start: {
|
console.log(game)
|
||||||
dateTime: '2023-03-22T09:00:00-07:00',
|
if (!isDuplicateEvent) {
|
||||||
timeZone: 'Israel'
|
newGamesAdded.push(game);
|
||||||
},
|
console.log("Event does not exist");
|
||||||
end: {
|
await this.googleCalendar.updateNewEvent([game]);
|
||||||
dateTime: '2023-03-22T09:00:00-07:00',
|
} else {
|
||||||
timeZone: 'Israel'
|
console.log("Event already exists");
|
||||||
},
|
}
|
||||||
recurrence: []
|
|
||||||
});
|
|
||||||
console.log("Updaing new event: " + upcomingEvents[0].summary)
|
|
||||||
console.log(upcomingEvents[0])
|
|
||||||
this.googleCalendar.updateNewEvent(upcomingEvents);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getHaifaIndex(teams: any) {
|
|
||||||
for (let i = 0; i < teams.length; i++) {
|
|
||||||
if (teams[i].name === "Maccabi Haifa") {
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.appendFileSync('cron.log', logMessage);
|
||||||
|
console.log(logMessage); // Optional: also log to console
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App();
|
const app = new App();
|
||||||
app.init();
|
|
||||||
app.getNewGamesAndUpdateCalendar();
|
cron.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"
|
||||||
|
});
|
||||||
|
|
|
@ -10,5 +10,4 @@ export interface GoogleCalendarEvent {
|
||||||
dateTime: string;
|
dateTime: string;
|
||||||
timeZone: string;
|
timeZone: string;
|
||||||
};
|
};
|
||||||
recurrence: string[];
|
|
||||||
}
|
}
|
3
start.sh
Normal file
3
start.sh
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
docker run -p 3000:3000 -d --restart=unless-stopped --name haifareminder docker.io/kfda89/haifareminder
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"target": "ES2019",
|
"target": "ES2019",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./public",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src"
|
"rootDir": "./src"
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
}
|
}
|
Loading…
Reference in a new issue