Skip to content

Commit

Permalink
feat!: create config file for reservation preferences (#54)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The reservation config now lives in an actual config file separate from the code implementation
  • Loading branch information
Alkaar authored Oct 12, 2022
1 parent 4b8f0f3 commit 75777f5
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 58 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ snipe a reservation. It will attempt to grab a reservation for a couple of secon
it was or wasn't successful in getting a reservation.

## Usage
You need to provide a few values before running the bot. These values are located in the `ResyConfig` object. There
are comments above the variables with what needs to be provided before it can be used, but I'll list it
here as well for clarity.
You need to provide a few values before running the bot. You can set these parameters in the `resyConfig.conf` file
which is located in the `resources` folder. There are comments above the properties with what needs to be provided
before it can be used, but I'll list it here as well for clarity.
* **apiKey** - Your user profile API key. Can be found when logging into Resy if you have the web console open in your
headers called `authorization`.
* **auth_token** - Your user profile authentication token when logging into Resy. Can be found when logging into Resy
Expand All @@ -36,14 +36,15 @@ allows full flexibility on your reservation preferences. For example, your prior

## How it works
The main entry point of the bot is in the `ResyBookingBot` object under the `main` function. It utilizes the arguments
which you need to provide under the `ResyConfig` object. The bot runs based on the local time of the machine it's
running on. Upon running the bot, it will automatically sleep until the specified time. At the specified time, it will
wake up and attempt to query for reservations for 10 seconds. This is because sometimes reservations are not available
exactly at the same time every day so 10 seconds is to allow for some buffer. Once reservation times are retrieved, it
will try to find the best available time slot given your priority list of reservation times. If a time can't be booked,
the bot will shutdown here. If a time can be booked, it will make an attempt to snipe it. If a reservation couldn't be
booked, and it's still within 10 seconds of the original start time, it will restart the whole workflow and try to find
another available reservation. In the event it was unable to get any reservations, the bot will automatically shutdown.
which you need to provide in the `resyConfig.conf` file, located in the `resources` folder. The bot runs based on the
local time of the machine it's running on. Upon running the bot, it will automatically sleep until the specified time.
At the specified time, it will wake up and attempt to query for reservations for 10 seconds. This is because sometimes
reservations are not available exactly at the same time every day so 10 seconds is to allow for some buffer. Once
reservation times are retrieved, it will try to find the best available time slot given your priority list of
reservation times. If a time can't be booked, the bot will shutdown here. If a time can be booked, it will make an
attempt to snipe it. If a reservation couldn't be booked, and it's still within 10 seconds of the original start time,
it will restart the whole workflow and try to find another available reservation. In the event it was unable to get any
reservations, the bot will automatically shutdown.

## Running the bot
There are a multitude of ways to run it, but I'll share the two most
Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ val root = Project("resy-booking-bot", file("."))
scalacOptions += "-Ywarn-unused",
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-ahc-ws" % "2.8.16",
"com.github.pureconfig" %% "pureconfig" % "0.17.1",
"org.apache.logging.log4j" %% "log4j-api-scala" % "12.0",
"org.apache.logging.log4j" % "log4j-core" % "2.13.0" % Runtime,
"org.scalatest" %% "scalatest" % "3.2.12" % Test,
Expand Down
49 changes: 49 additions & 0 deletions src/main/resources/resyConfig.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
############
# ResyKeys #
############
# Your user profile API key which can be found via your browser web console in your headers called "authorization"
# e.g.
# resyKeys.api-key="MY_API_KEY"
resyKeys.api-key=
# Your user profile authentication token which can be found via your browser web console in your headers called
# "x-resy-auth-token"
# e.g.
# resyKeys.auth-token="MY_AUTH_TOKEN"
resyKeys.auth-token=

######################
# ReservationDetails #
######################
# Date of the reservation in YYYY-MM-DD format
# e.g.
# resDetails.date="2099-01-30"
resDetails.date=
# Size of the party reservation
# e.g.
# resDetails.party-size=2
resDetails.party-size=
# Unique identifier of the restaurant where you want to make the reservation
# e.g.
# resDetails.venue-id=123
resDetails.venue-id=
# Priority list of reservation times and table types. Time is in military time HH:MM:SS format. If no preference on
# table type, then simply don't set it.
# e.g.
# resDetails.res-time-types=[
# {reservation-time="18:00:00", table-type="Dining Room"},
# {reservation-time="18:00:00", table-type="Patio"},
# {reservation-time="18:15:00"}
# ]
resDetails.res-time-types=

#############
# SnipeTime #
#############
# Hour of the day when reservations become available and when you want to snipe
# e.g.
# snipeTime.hours=9
snipeTime.hours=
# Minute of the day when reservations become available and when you want to snipe
# e.g.
# snipeTime.minutes=0
snipeTime.minutes=
28 changes: 7 additions & 21 deletions src/main/scala/com/resy/ResyBookingBot.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.resy

import akka.actor.ActorSystem
import com.resy.ResyConfig._
import org.apache.logging.log4j.scala.Logging
import org.joda.time.DateTime
import pureconfig.ConfigSource
import pureconfig.generic.auto._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
Expand All @@ -14,6 +15,11 @@ object ResyBookingBot extends Logging {
def main(args: Array[String]): Unit = {
logger.info("Starting Resy Booking Bot")

val resyConfig = ConfigSource.resources("resyConfig.conf")
val resyKeys = resyConfig.at("resyKeys").loadOrThrow[ResyKeys]
val resDetails = resyConfig.at("resDetails").loadOrThrow[ReservationDetails]
val snipeTime = resyConfig.at("snipeTime").loadOrThrow[SnipeTime]

val resyApi = new ResyApi(resyKeys)
val resyClient = new ResyClient(resyApi)
val resyBookingWorkflow = new ResyBookingWorkflow(resyClient, resDetails)
Expand Down Expand Up @@ -49,23 +55,3 @@ object ResyBookingBot extends Logging {
}
}
}

final case class ResyKeys(apiKey: String, authToken: String)

final case class ReservationDetails(
date: String,
partySize: Int,
venueId: Int,
resTimeTypes: Seq[ReservationTimeType]
)

final case class ReservationTimeType(reservationTime: String, tableType: Option[String] = None)

object ReservationTimeType {

def apply(reservationTime: String, tableType: String): ReservationTimeType = {
ReservationTimeType(reservationTime, Some(tableType))
}
}

final case class SnipeTime(hours: Int, minutes: Int)
41 changes: 15 additions & 26 deletions src/main/scala/com/resy/ResyConfig.scala
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
package com.resy

object ResyConfig {
final case class ResyKeys(apiKey: String, authToken: String)

val resyKeys: ResyKeys = ResyKeys(
// Your user profile API key which can be found via your browser web console in your headers
// called "authorization"
apiKey = ???,
// Your user profile authentication token which can be found via your browser web console in
// your headers called "x-resy-auth-token"
authToken = ???
)
final case class ReservationDetails(
date: String,
partySize: Int,
venueId: Int,
resTimeTypes: Seq[ReservationTimeType]
)

val resDetails: ReservationDetails = ReservationDetails(
// Date of the reservation in YYYY-MM-DD format
date = ???,
// Size of the party reservation
partySize = ???,
// Unique identifier of the restaurant where you want to make the reservation
venueId = ???,
// Priority list of reservation times and table types. Time is in military time HH:MM:SS format.
// If no preference on table type, then simply don't set it.
resTimeTypes = ???
)
final case class ReservationTimeType(reservationTime: String, tableType: Option[String] = None)

val snipeTime: SnipeTime = SnipeTime(
// Hour of the day when reservations become available and when you want to snipe
hours = ???,
// Minute of the day when reservations become available and when you want to snipe
minutes = ???
)
object ReservationTimeType {

def apply(reservationTime: String, tableType: String): ReservationTimeType = {
ReservationTimeType(reservationTime, Some(tableType))
}
}

final case class SnipeTime(hours: Int, minutes: Int)

0 comments on commit 75777f5

Please sign in to comment.