-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
193 lines (156 loc) · 5.72 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import axios from 'axios';
import moment from 'moment';
import { URL } from 'url';
import dotenv from 'dotenv';
import cron, {ScheduledTask} from 'node-cron';
dotenv.config();
export async function fetchAvailabilities(): Promise<string[]> {
try {
// Get the URL from the environment variable
let url = process.env.APPOINTMENT_URL || '';
// Check if the URL is defined
if (!url) {
throw new Error('The APPOINTMENT_URL environment variable is not defined.');
}
// Replace the start_date with today's date
const today = moment().format('YYYY-MM-DD');
url = url.replace(/start_date=\d{4}-\d{2}-\d{2}/, `start_date=${today}`);
// Check if the URL is valid
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
} catch (error) {
throw new Error('The APPOINTMENT_URL environment variable is not a valid URL.');
}
console.log(`Fetching availabilities for ${url}`);
// Fetch the availabilities.json
const response = await axios.get<{next_slot: string}>(parsedUrl.toString());
// Check if next_slot is available
const nextSlot = response.data.next_slot;
if (nextSlot) {
const availableDates = [moment(nextSlot).format('YYYY-MM-DD')];
console.log('Fetch complete.');
return availableDates;
} else {
console.log('No next_slot available.');
return [];
}
} catch (error) {
console.error(`Error fetching availabilities: ${error}`);
return [];
}
}
export async function availableAppointment(dates: string[], timespan: number): Promise<string> {
// Check if the timespan is a valid number
if (isNaN(timespan)) {
throw new Error('The TIMESPAN_DAYS environment variable is not a valid number.');
}
console.log('Checking for available appointments...');
// Get the current date and the date after the given timespan
const now = moment();
const futureDate = moment().add(timespan, 'days');
// Sort the dates array
dates.sort();
// Check if there's a date within the timespan
for (const date of dates) {
const appointmentDate = moment(date);
if (appointmentDate.isBetween(now, futureDate, 'day', '[]')) {
console.log(`Found an available appointment on ${date}.`);
return date;
}
}
console.log('No available appointments found.');
return '';
}
export async function sendSlackNotification(date: string): Promise<void> {
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
const bookingUrl = process.env.DOCTOR_BOOKING_URL;
if (!slackWebhookUrl) {
throw new Error('The SLACK_WEBHOOK_URL environment variable is not defined.');
}
if (!bookingUrl) {
throw new Error('The DOCTOR_BOOKING_URL environment variable is not defined.');
}
const message = {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `:pill: An appointment is available on *${date}* :calendar:. You can book it here: ${bookingUrl}`
}
},
]
};
await axios.post(slackWebhookUrl, message);
}
// Usage
let task: ScheduledTask;
async function checkAppointmentAvailability() {
// Get the timespan from the environment variable
const timespan = Number(process.env.TIMESPAN_DAYS || '0');
const stopWhenFound = process.env.STOP_WHEN_FOUND?.toLowerCase() === 'true'
try {
const dates = await fetchAvailabilities();
const date = await availableAppointment(dates, timespan);
if (date) {
console.log(`Next available appointment is on: ${date}`);
await sendSlackNotification(date).catch((error) => {
console.error(`Error while sending Slack notification: ${error}`);
});
// Stop the task once an appointment is found
if (task && stopWhenFound) {
console.log('Appointment found. Stopping the task...');
task.stop();
}
} else {
console.log(`No appointments available within the specified timespan (${timespan} days).`);
}
} catch (error) {
console.error(`Error while checking appointment availability: ${error}`);
}
}
export async function sendInitialSlackNotification(): Promise<void> {
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
const bookingUrl = process.env.DOCTOR_BOOKING_URL;
const timespan = Number(process.env.TIMESPAN_DAYS || '0');
const schedule = process.env.SCHEDULE || '* * * * *';
if (!slackWebhookUrl) {
throw new Error('The SLACK_WEBHOOK_URL environment variable is not defined.');
}
if (!bookingUrl) {
throw new Error('The DOCTOR_BOOKING_URL environment variable is not defined.');
}
const message = {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `:robot_face: The Doctolib Appointment Finder has started checking every ${schedule} for available appointments within the next ${timespan} days. You will receive notifications when appointments become available. The booking link is: ${bookingUrl}`
}
},
]
};
await axios.post(slackWebhookUrl, message);
}
if (typeof jest !== 'undefined') {
console.log('Running in Jest environment');
} else {
// Get the schedule from the environment variable
const schedule = process.env.SCHEDULE || '* * * * *';
// Schedule the function using node-cron
if (!cron.validate(schedule)) {
console.error('The SCHEDULE environment variable is not a valid cron expression.');
} else {
console.log(`Scheduling appointment availability check every ${schedule}.`);
try {
task = cron.schedule(schedule, checkAppointmentAvailability);
sendInitialSlackNotification().catch((error) => {
console.error(`Error while sending initial Slack notification: ${error}`);
});
} catch (error) {
console.error(`Error while scheduling appointment availability check: ${error}`);
}
}
}