Skip to content

Commit

Permalink
feat: Add github action to run the command
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhiyuanMa2017 committed Sep 30, 2024
1 parent 27fdb77 commit 146da98
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 65 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/go-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: Go Run
on:
push:
branches:
- main
- master
schedule:
- cron: '00 * * * *'
- cron: '30 * * * *'
jobs:
go_run:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -39,5 +39,4 @@ jobs:
- name: Run
run: |
go run ./cmd/server -appt_type=driver-license-renewal -database_path=./database/ncdmv.db -locations=cary,clayton,durham-east,durham-south,fuquay-varina,garner,raleigh-east,raleigh-north,raleigh-west,wendell --discord_webhook=https://discord.com/api/webhooks/1289829329596973119/dArKKjlXCzBs0UUrWHN4uUxJkxm4jU0IN7m6rT8d3PJr_HqkLMMll4aoLoWQRQk32Hbf
go run ./cmd/server -appt_type=driver-license-renewal -database_path=./database/ncdmv.db -locations=cary,clayton,durham-east,durham-south,fuquay-varina,garner,raleigh-east,raleigh-north,raleigh-west,wendell --discord_webhook=${{ secrets.DISCORD_WEBHOOK_URL }}
4 changes: 2 additions & 2 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func main() {
}

// Initialize the Chrome context and open a new window.
ctx, cancel, err := ncdmv.NewChromeContext(ctx, *headless, disableGpu, *debug)
ctx, cancel, err := ncdmv.NewChromeContext(ctx, true, true, *debug)
if err != nil {
log.Fatalf("Failed to init Chrome context: %s", err)
}
Expand All @@ -100,7 +100,7 @@ func main() {
parsedTimeout := time.Duration(*timeout) * time.Second
parsedInterval := time.Duration(*interval) * time.Minute

if err := client.Start(ctx, parsedApptType, parsedLocations, parsedTimeout, parsedInterval); err != nil {
if err := client.Start(ctx, parsedApptType, parsedLocations, parsedTimeout, parsedInterval, *locations); err != nil {
log.Fatal(err)
}
}
120 changes: 61 additions & 59 deletions pkg/ncdmv/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,9 @@ func (c Client) RunForLocations(ctx context.Context, apptType AppointmentType, l
return appointments, nil
}

func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType, appointmentsToNotify []models.Appointment, discordWebhook string, interval time.Duration) error {
func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType, appointmentsToNotify []Appointment, discordWebhook string, interval time.Duration) error {
// Sort appointments by time.
slices.SortFunc(appointmentsToNotify, func(a, b models.Appointment) int {
slices.SortFunc(appointmentsToNotify, func(a, b Appointment) int {
if a.Time.Before(b.Time) {
return -1
} else if a.Time.After(b.Time) {
Expand All @@ -441,9 +441,9 @@ func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType,
})

// Group appointments by location.
appointmentsByLocation := make(map[string][]models.Appointment)
appointmentsByLocation := make(map[string][]Appointment)
for _, a := range appointmentsToNotify {
appointmentsByLocation[a.Location] = append(appointmentsByLocation[a.Location], a)
appointmentsByLocation[a.Location.String()] = append(appointmentsByLocation[a.Location.String()], a)
}

// Sort locations by name.
Expand All @@ -453,6 +453,12 @@ func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType,
}
slices.Sort(locations)

if len(locations) == 0 {
if err := c.sendDiscordMessage("No availabilities found for the locations\n"); err != nil {
log.Printf("Failed to send message to Discord webhook %q: %v", c.discordWebhook, err)
}
}

for i, location := range locations {
appointments := appointmentsByLocation[location]
b := strings.Builder{}
Expand All @@ -475,11 +481,7 @@ func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType,
b.WriteString(" - `(... more appointments available)`\n")
break
}
if appointment.Available {
b.WriteString(fmt.Sprintf(" - :white_check_mark: `%s`\n", appointment.Time.String()))
} else if c.notifyUnavailable {
b.WriteString(fmt.Sprintf(" - :x: `%s`\n", appointment.Time.String()))
}
b.WriteString(fmt.Sprintf(" - :white_check_mark: `%s`\n", appointment.Time.String()))
}

if i == len(locations)-1 {
Expand All @@ -494,16 +496,16 @@ func (c Client) sendNotifications(ctx context.Context, apptType AppointmentType,
}

// Mark all of the appointments in the batch as "notified".
for _, appointment := range appointments {
if _, err := c.db.CreateNotification(ctx, models.CreateNotificationParams{
AppointmentID: appointment.ID,
DiscordWebhook: sql.NullString{String: discordWebhook, Valid: true},
Available: appointment.Available,
ApptType: apptType.String(),
}); err != nil {
return fmt.Errorf("failed to create notification for appointment %v: %w", appointment, err)
}
}
//for _, appointment := range appointments {
// if _, err := c.db.CreateNotification(ctx, models.CreateNotificationParams{
// AppointmentID: 1,
// DiscordWebhook: sql.NullString{String: discordWebhook, Valid: true},
// Available: true,
// ApptType: apptType.String(),
// }); err != nil {
// return fmt.Errorf("failed to create notification for appointment %v: %w", appointment, err)
// }
//}

time.Sleep(interval)
}
Expand Down Expand Up @@ -588,14 +590,14 @@ func (c Client) listExistingAppointmentsInLocations(ctx context.Context, t time.
func (c Client) handleTick(ctx context.Context, apptType AppointmentType, locations []Location, timeout time.Duration) error {
now := time.Now()

// Prune all invalid appointments (i.e., those that are in the past) by setting them as unavailable.
rows, err := c.db.PruneAppointmentsBeforeDate(ctx, now)
if err != nil {
return fmt.Errorf("failed to delete appointments before current time (%v): %w", now, err)
}
if len(rows) > 0 {
slog.Info("Pruned invalid appointments", "count", len(rows))
}
//// Prune all invalid appointments (i.e., those that are in the past) by setting them as unavailable.
//rows, err := c.db.PruneAppointmentsBeforeDate(ctx, now)
//if err != nil {
// return fmt.Errorf("failed to delete appointments before current time (%v): %w", now, err)
//}
//if len(rows) > 0 {
// slog.Info("Pruned invalid appointments", "count", len(rows))
//}

existingAppointments, err := c.listExistingAppointmentsInLocations(ctx, now, locations)
if err != nil {
Expand Down Expand Up @@ -650,9 +652,9 @@ func (c Client) handleTick(ctx context.Context, apptType AppointmentType, locati
slog.Info("Updated appointments successfully", "count", len(appointmentsToUpdate))
}

if err := c.sendNotifications(ctx, apptType, appointmentsToNotify, c.discordWebhook, 1*time.Second); err != nil {
return fmt.Errorf("failed to send notifications: %w", err)
}
//if err := c.sendNotifications(ctx, apptType, appointmentsToNotify, c.discordWebhook, 1*time.Second); err != nil {
// return fmt.Errorf("failed to send notifications: %w", err)
//}
if len(appointmentsToNotify) > 0 {
slog.Info("Sent notifications successfully", "count", len(appointmentsToNotify))
}
Expand All @@ -671,42 +673,42 @@ func (c Client) handleTick(ctx context.Context, apptType AppointmentType, locati
// Each provided location is processed in a _separate_ Chrome browser tab. This allows for some degree of parallelism
// as each tab can run independently of the others. The downside is that the list of locations needs to bounded based
// on the resources available on your machine.
func (c Client) Start(ctx context.Context, apptType AppointmentType, locations []Location, timeout, interval time.Duration) error {
t := time.NewTicker(interval)
defer t.Stop()
func (c Client) Start(ctx context.Context, apptType AppointmentType, locations []Location, timeout, interval time.Duration, locationsString string) error {

slog.Info("Starting client", "appt_type", apptType, "locations", locations, "timeout", timeout, "interval", interval)

tick := func() error {
defer slog.Info("Sleeping between location checks...", "interval", interval)
for {
if err := c.handleTick(ctx, apptType, locations, timeout); err != nil {
if strings.Contains(err.Error(), temporaryErrString) {
slog.Warn("handleTick failed with temporary error; retrying tick...")
continue
}
slog.Error("handleTick failed", "err", err)
if c.stopOnFailure {
return err
}
}
return nil
b := strings.Builder{}
b.WriteString(fmt.Sprintf("Start searching at %s\n", time.Now().Format("2006-01-02 15:04:05")))
b.WriteString(fmt.Sprintf("- Appointment type: %s\n", apptType))
b.WriteString("- Locations: ")
b.WriteString(strings.Join(strings.Split(locationsString, ","), ", "))
b.WriteString("\n")

if err := c.sendDiscordMessage(b.String()); err != nil {
log.Printf("Failed to send message to Discord webhook %q: %v", c.discordWebhook, err)
}
}

for {
// Trigger a "tick" immediately as the ticker does not do so for us.
if err := tick(); err != nil {
return err
}
// Block until the next tick or the context is cancelled.
select {
case <-t.C:
if err := tick(); err != nil {
return err
var appointments []*Appointment
for _, location := range locations {
appts, err := c.RunForLocations(ctx, apptType, []Location{location}, timeout)
if err != nil {
return fmt.Errorf("failed to check locations: %w", err)
}
case <-ctx.Done():
return ctx.Err()
appointments = append(appointments, appts...)
}

values := make([]Appointment, len(appointments))
for i, appt := range appointments {
values[i] = *appt
}
if err := c.sendNotifications(ctx, apptType, values, c.discordWebhook, 1*time.Second); err != nil {
return fmt.Errorf("failed to send notifications: %w", err)
}
if len(appointments) > 0 {
slog.Info("Sent notifications successfully", "count", len(appointments))
}
return nil
}
return tick()
}

0 comments on commit 146da98

Please sign in to comment.