Skip to content

Instantly share code, notes, and snippets.

@fcallejon
Last active November 26, 2023 18:54
Show Gist options
  • Save fcallejon/38520cdfcf4654e3899540ad26d62696 to your computer and use it in GitHub Desktop.
Save fcallejon/38520cdfcf4654e3899540ad26d62696 to your computer and use it in GitHub Desktop.
Check Irish Garda E-Vetting
#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