advent-of-code-api 0.2.7.1 → 0.2.8.0
raw patch · 5 files changed
+86/−27 lines, 5 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Advent: NextDayTime :: Day -> Int -> NextDayTime
+ Advent: NoNextDayTime :: NextDayTime
+ Advent: [AoCNextDayTime] :: AoC NextDayTime
+ Advent: data NextDayTime
+ Advent.API: instance Advent.API.FromTags "script" Advent.Types.NextDayTime
+ Advent.API: type Scripts = HTMLTags "script"
+ Advent.Types: NextDayTime :: Day -> Int -> NextDayTime
+ Advent.Types: NoNextDayTime :: NextDayTime
+ Advent.Types: data NextDayTime
+ Advent.Types: instance GHC.Classes.Eq Advent.Types.NextDayTime
+ Advent.Types: instance GHC.Classes.Ord Advent.Types.NextDayTime
+ Advent.Types: instance GHC.Generics.Generic Advent.Types.NextDayTime
+ Advent.Types: instance GHC.Show.Show Advent.Types.NextDayTime
- Advent.API: adventAPIClient :: Integer -> (Day -> ClientM (Map Part Text) :<|> (ClientM Text :<|> (SubmitInfo -> ClientM (Text :<|> SubmitRes)))) :<|> (ClientM GlobalLeaderboard :<|> ((Day -> ClientM DailyLeaderboard) :<|> (PublicCode -> ClientM Leaderboard)))
+ Advent.API: adventAPIClient :: Integer -> ClientM NextDayTime :<|> ((Day -> ClientM (Map Part Text) :<|> (ClientM Text :<|> (SubmitInfo -> ClientM (Text :<|> SubmitRes)))) :<|> (ClientM GlobalLeaderboard :<|> ((Day -> ClientM DailyLeaderboard) :<|> (PublicCode -> ClientM Leaderboard))))
- Advent.API: type AdventAPI = Capture "year" Integer :> ("day" :> Capture "day" Day :> (Get '[Articles] (Map Part Text) :<|> "input" :> Get '[RawText] Text :<|> "answer" :> ReqBody '[FormUrlEncoded] SubmitInfo :> Post '[Articles] (Text :<|> SubmitRes)) :<|> "leaderboard" :> (Get '[Divs] GlobalLeaderboard :<|> "day" :> Capture "day" Day :> Get '[Divs] DailyLeaderboard :<|> "private" :> "view" :> Capture "code" PublicCode :> Get '[JSON] Leaderboard))
+ Advent.API: type AdventAPI = Capture "year" Integer :> (Get '[Scripts] NextDayTime :<|> "day" :> Capture "day" Day :> (Get '[Articles] (Map Part Text) :<|> "input" :> Get '[RawText] Text :<|> "answer" :> ReqBody '[FormUrlEncoded] SubmitInfo :> Post '[Articles] (Text :<|> SubmitRes)) :<|> ("leaderboard" :> (Get '[Divs] GlobalLeaderboard :<|> "day" :> Capture "day" Day :> Get '[Divs] DailyLeaderboard :<|> "private" :> "view" :> Capture "code" PublicCode :> Get '[JSON] Leaderboard)))
Files
- CHANGELOG.md +11/−0
- advent-of-code-api.cabal +2/−2
- src/Advent.hs +22/−4
- src/Advent/API.hs +43/−21
- src/Advent/Types.hs +8/−0
CHANGELOG.md view
@@ -1,6 +1,17 @@ Changelog ========= +Version 0.2.8.0+---------------++*December 14, 2020*++<https://github.com/mstksg/advent-of-code-api/releases/tag/v0.2.8.0>++* Add servant endpoint to query calendar page for a year to infer next puzzle+ and time to puzzle in seconds, backed by the `NextDayTime` data type.+* Add `AoCNextDayTime` to `AoC` to support the above operation.+ Version 0.2.7.1 ---------------
advent-of-code-api.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 42fe072e1e23c3a691ec787cf5e1118785d56470c1ec117048fc826c4e47609c+-- hash: 6cf4283a8e57b7716c98e2f2914b0396dc18f396a3ff0bb7f5df28750eefbb92 name: advent-of-code-api-version: 0.2.7.1+version: 0.2.8.0 synopsis: Advent of Code REST API bindings and servant API description: Haskell bindings for Advent of Code REST API and a servant API. Please use responsibly! See README.md or "Advent" module for an introduction and
src/Advent.hs view
@@ -50,6 +50,7 @@ AoC(..) , Part(..) , Day(..)+ , NextDayTime(..) , AoCOpts(..) , SubmitRes(..), showSubmitRes , runAoC@@ -224,6 +225,18 @@ AoCGlobalLeaderboard :: AoC GlobalLeaderboard + -- | From the calendar, fetch the next release's day and the+ -- number of seconds util its release, if there is any at all.+ --+ -- This does an actual request to the AoC servers, and is only accurate+ -- to the second; to infer this information (to the millisecond level)+ -- from the system clock, you should probably use 'timeToRelease' and+ -- 'aocServerTime' instead, which requires no network requests.+ --+ -- @since 0.2.8.0+ AoCNextDayTime+ :: AoC NextDayTime+ deriving instance Show (AoC a) deriving instance Typeable (AoC a) @@ -235,6 +248,7 @@ aocDay (AoCLeaderboard _) = Nothing aocDay (AoCDailyLeaderboard d) = Just d aocDay AoCGlobalLeaderboard = Nothing+aocDay AoCNextDayTime = Nothing -- | A possible (syncronous, logical, pure) error returnable from 'runAoC'. -- Does not cover any asynchronous or IO errors.@@ -313,12 +327,14 @@ AoCInput i -> let _ :<|> r :<|> _ = adventAPIPuzzleClient yr i in r AoCSubmit i p ans -> let _ :<|> _ :<|> r = adventAPIPuzzleClient yr i in r (SubmitInfo p ans) <&> \(x :<|> y) -> (x, y)- AoCLeaderboard c -> let _ :<|> _ :<|> _ :<|> r = adventAPIClient yr+ AoCLeaderboard c -> let _ :<|> _ :<|> _ :<|> _ :<|> r = adventAPIClient yr in r (PublicCode c)- AoCDailyLeaderboard d -> let _ :<|> _ :<|> r :<|> _ = adventAPIClient yr+ AoCDailyLeaderboard d -> let _ :<|> _ :<|> _ :<|> r :<|> _ = adventAPIClient yr in r d- AoCGlobalLeaderboard -> let _ :<|> r :<|> _ :<|> _ = adventAPIClient yr+ AoCGlobalLeaderboard -> let _ :<|> _ :<|> r :<|> _ = adventAPIClient yr in r+ AoCNextDayTime -> let r :<|> _ = adventAPIClient yr+ in r -- | Cache file for a given 'AoC' command@@ -332,8 +348,9 @@ AoCInput d -> Just $ printf "input/%s%04d/%02d.txt" keyDir yr (dayInt d) AoCSubmit{} -> Nothing AoCLeaderboard{} -> Nothing- AoCDailyLeaderboard d -> Just $ printf "daily/%04d/%02d.json" yr (dayInt d)+ AoCDailyLeaderboard d -> Just $ printf "daily/%04d/%02d.json" yr (dayInt d) AoCGlobalLeaderboard{} -> Just $ printf "global/%04d.json" yr+ AoCNextDayTime -> Nothing where keyDir = case sess of Nothing -> ""@@ -443,6 +460,7 @@ guard $ not evt' -- only load cache if evt' is false: it was saved in a non-evt time pure $ Right lb }+ AoCNextDayTime{} -> noCache where expectedParts :: Set Part expectedParts
src/Advent/API.hs view
@@ -42,38 +42,40 @@ , FromTags(..) , Articles , Divs+ , Scripts , RawText -- * Internal , processHTML ) where import Advent.Types+import Control.Applicative import Control.Monad import Control.Monad.State import Data.Bifunctor import Data.Char import Data.Finite import Data.Foldable-import Data.List.NonEmpty (NonEmpty(..))-import Data.Map (Map)+import Data.List.NonEmpty (NonEmpty(..))+import Data.Map (Map) import Data.Maybe import Data.Ord import Data.Proxy-import Data.Text (Text)-import Data.Time hiding (Day)+import Data.Text (Text)+import Data.Time hiding (Day) import GHC.TypeLits import Servant.API import Servant.Client-import Text.HTML.TagSoup.Tree (TagTree(..))-import Text.Read (readMaybe)-import qualified Data.ByteString.Lazy as BSL-import qualified Data.List.NonEmpty as NE-import qualified Data.Map as M-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Network.HTTP.Media as M-import qualified Text.HTML.TagSoup as H-import qualified Text.HTML.TagSoup.Tree as H+import Text.HTML.TagSoup.Tree (TagTree(..))+import Text.Read (readMaybe)+import qualified Data.ByteString.Lazy as BSL+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Network.HTTP.Media as M+import qualified Text.HTML.TagSoup as H+import qualified Text.HTML.TagSoup.Tree as H #if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>))@@ -106,6 +108,9 @@ -- @since 0.2.3.0 type Divs = HTMLTags "div" +-- | Interpret a response as a list of HTML 'T.Text' found in @<script>@ tags.+type Scripts = HTMLTags "script"+ -- | Class for interpreting a list of 'T.Text' in tags to some desired -- output. --@@ -205,30 +210,46 @@ xs' <- forM xs $ \x -> x { glbmRank = Rank currScore } <$ modify succ pure (Rank currScore, xs') -+instance FromTags "script" NextDayTime where+ fromTags _ = (<|> Just NoNextDayTime) . listToMaybe . mapMaybe findNDT+ where+ -- var server_eta = 25112;+ -- var key = "2020-15-"+server_eta;+ findNDT body = do+ eta <- T.unpack <$> grabKey "server_eta" body+ yd <- grabKey "key" body+ sec <- readMaybe eta+ dayStr <- listToMaybe . drop 1 . T.splitOn "-" $ yd+ dy <- mkDay =<< readMaybe (T.unpack dayStr)+ pure $ NextDayTime dy sec+ grabKey t str =+ fst . T.breakOn ";\n" <$> T.stripPrefix t' (snd (T.breakOn t' str))+ where+ t' = "var " <> t <> " = " -- | REST API of Advent of Code. -- -- Note that most of these requests assume a "session=" cookie. type AdventAPI = Capture "year" Integer- :> ("day" :> Capture "day" Day+ :> (Get '[Scripts] NextDayTime+ :<|> "day" :> Capture "day" Day :> (Get '[Articles] (Map Part Text) :<|> "input" :> Get '[RawText] Text :<|> "answer" :> ReqBody '[FormUrlEncoded] SubmitInfo :> Post '[Articles] (Text :<|> SubmitRes) )- :<|> "leaderboard"+ :<|> ("leaderboard" :> (Get '[Divs] GlobalLeaderboard :<|> "day" :> Capture "day" Day :> Get '[Divs] DailyLeaderboard :<|> "private" :> "view" :> Capture "code" PublicCode :> Get '[JSON] Leaderboard-- )+ )) ) + -- | 'Proxy' used for /servant/ functions. adventAPI :: Proxy AdventAPI adventAPI = Proxy@@ -236,7 +257,8 @@ -- | 'ClientM' requests based on 'AdventAPI', generated by servant. adventAPIClient :: Integer- -> (Day -> ClientM (Map Part Text) :<|> ClientM Text :<|> (SubmitInfo -> ClientM (Text :<|> SubmitRes)) )+ -> ClientM NextDayTime+ :<|> (Day -> ClientM (Map Part Text) :<|> ClientM Text :<|> (SubmitInfo -> ClientM (Text :<|> SubmitRes)) ) :<|> ClientM GlobalLeaderboard :<|> (Day -> ClientM DailyLeaderboard) :<|> (PublicCode -> ClientM Leaderboard)@@ -250,7 +272,7 @@ -> ClientM (Map Part Text) :<|> ClientM Text :<|> (SubmitInfo -> ClientM (Text :<|> SubmitRes)) adventAPIPuzzleClient y = pis where- pis :<|> _ = adventAPIClient y+ _ :<|> pis :<|> _ = adventAPIClient y userNameNaked :: [TagTree Text] -> Maybe Text userNameNaked = (listToMaybe .) . mapMaybe $ \x -> do
src/Advent/Types.hs view
@@ -41,6 +41,7 @@ , DailyLeaderboardMember(..) , GlobalLeaderboard(..) , GlobalLeaderboardMember(..)+ , NextDayTime(..) -- * Util , mkDay, mkDay_, dayInt , _DayInt, pattern DayInt@@ -246,6 +247,13 @@ } deriving (Show, Eq, Ord, Typeable, Generic) +-- | The next day for a challenge in a given year, and also the number of+-- seconds until the challenge is released.+--+-- @since 0.2.8.0+data NextDayTime = NextDayTime Day Int+ | NoNextDayTime+ deriving (Show, Eq, Ord, Typeable, Generic) instance ToHttpApiData Part where toUrlPiece = T.pack . show . partInt