diff --git a/README.md b/README.md index 7263fa4..6ccedf4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/build.sbt b/build.sbt index 8be6f6e..b33ce9b 100644 --- a/build.sbt +++ b/build.sbt @@ -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, diff --git a/src/main/resources/resyConfig.conf b/src/main/resources/resyConfig.conf new file mode 100644 index 0000000..6cdb3b4 --- /dev/null +++ b/src/main/resources/resyConfig.conf @@ -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= \ No newline at end of file diff --git a/src/main/scala/com/resy/ResyBookingBot.scala b/src/main/scala/com/resy/ResyBookingBot.scala index f68d85f..aa9d151 100644 --- a/src/main/scala/com/resy/ResyBookingBot.scala +++ b/src/main/scala/com/resy/ResyBookingBot.scala @@ -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._ @@ -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) @@ -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) diff --git a/src/main/scala/com/resy/ResyConfig.scala b/src/main/scala/com/resy/ResyConfig.scala index 4c059cc..6b56d18 100644 --- a/src/main/scala/com/resy/ResyConfig.scala +++ b/src/main/scala/com/resy/ResyConfig.scala @@ -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)