Last active
November 26, 2023 18:54
-
-
Save fcallejon/38520cdfcf4654e3899540ad26d62696 to your computer and use it in GitHub Desktop.
Check Irish Garda E-Vetting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#r "nuget: FSharp.Data" | |
#r "nuget: FSharpPlus" | |
open System | |
open System.Threading.Tasks | |
open System.Net.Http | |
open FSharpPlus | |
open FSharp.Data | |
[<AutoOpen>] | |
module Prelude = | |
let inline isNullOrWhiteSpace v = String.IsNullOrWhiteSpace v | |
let (|NotNullOrWhiteSpace|NullString|WhiteSpace|) (v: string) = | |
if isNullOrWhiteSpace v then | |
if v = null then NullString else WhiteSpace | |
else if (v.Trim() |> String.IsNullOrEmpty) then | |
WhiteSpace | |
else | |
NotNullOrWhiteSpace v | |
type TaskResult<'T, 'E> = Task<Result<'T, 'E>> | |
module Task = | |
let inline catch (t: Task<'T>) = | |
task { | |
try | |
let! r = t | |
return Ok r | |
with e -> | |
return Error e | |
} | |
module TaskResult = | |
let inline map ([<InlineIfLambda>] f) t = (Task.map << Result.map) f t | |
let inline bind ([<InlineIfLambda>] f) t = | |
task { | |
match! t with | |
| Ok v -> return! f v | |
| Error e -> return Error e | |
} | |
[<RequireQualifiedAccess>] | |
module Option = | |
let inline ofString (v: string) = | |
match v with | |
| NotNullOrWhiteSpace s -> Some s | |
| _ -> None | |
type TimelineItem = { Date: string; Description: string } | |
let trim (s: string) = s.Trim() | |
let parseDateTime (date: string) (timeString: string) = | |
$"{date} {timeString}" | |
let parseTimelineItem (node: HtmlNode) (dateOnly: string) = | |
let descriptions date = | |
node.CssSelect(".bolder") | |
|> Seq.choose (fun x -> x.InnerText() |> Option.ofString |> Option.map trim) | |
|> Seq.map (fun x -> { Date = date; Description = x }) | |
node.CssSelect(".timeline-date") | |
|> Seq.tryHead | |
|> Option.bind (fun x -> x.InnerText() |> Option.ofString |> Option.map (trim >> (parseDateTime dateOnly))) | |
|> Option.map descriptions | |
|> Option.defaultValue empty | |
let parseTimeline (html: string) = | |
let doc = | |
"<html><body>" | |
+ html | |
+ "</body></html>" | |
|> HtmlNode.Parse | |
doc.CssSelect(".timeline-container") | |
|> Seq.collect (fun x -> | |
x.CssSelect(".timeline-label") | |
|> Seq.choose (fun x -> x.InnerText() |> Option.ofString |> Option.map trim) | |
|> Seq.collect (parseTimelineItem x)) | |
if Array.length fsi.CommandLineArgs < 3 then | |
printfn "Usage: dotnet fsi check.fsx <date of birth> <application id>" | |
exit 1 | |
let dateOfBirth = fsi.CommandLineArgs[1] | |
let applicationId = fsi.CommandLineArgs[2] | |
let timeline = | |
use client = new HttpClient() | |
use request = | |
new HttpRequestMessage(System.Net.Http.HttpMethod.Post, "https://vetting.garda.ie/Track/History") | |
request.Content <- | |
new StringContent($"dateOfBirth={dateOfBirth}&applicationId={applicationId}", System.Text.Encoding.UTF8, "application/x-www-form-urlencoded") | |
client.SendAsync request | |
|> Task.catch | |
|> TaskResult.bind (fun x -> x.Content.ReadAsStringAsync() |> Task.catch) | |
|> TaskResult.map parseTimeline | |
|> (fun x -> x.GetAwaiter().GetResult()) | |
match timeline with | |
| Ok timeline -> | |
let longest = timeline |> Seq.maxBy (fun x -> x.Description.Length) | |
let bottomLine = new String('-', longest.Description.Length + 7 + 18) | |
let topLine = new String(' ', bottomLine.Length - "| Date | Description|".Length ) | |
printfn "%s" bottomLine | |
printfn "| Date | Description%s|\r\n%s" topLine bottomLine | |
timeline | |
|> Seq.rev | |
|> Seq.iter (fun x -> | |
let datePadding, padding = | |
if x.Date.Length < 18 then | |
" ", new String(' ', bottomLine.Length - x.Description.Length - x.Date.Length - 8) | |
else | |
" ", new String(' ', bottomLine.Length - x.Description.Length - x.Date.Length - 7) | |
printfn "| %s%s| %s%s |\r\n%s" x.Date datePadding x.Description padding bottomLine) | |
| Error e -> | |
failwith e.Message | |
failwith e.StackTrace |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment