Skip to content

Instantly share code, notes, and snippets.

@Darkle
Created May 6, 2024 05:35
Show Gist options
  • Save Darkle/e2ea7ca35617bcea0340eb256ea9a476 to your computer and use it in GitHub Desktop.
Save Darkle/e2ea7ca35617bcea0340eb256ea9a476 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S dotnet fsi
#r "nuget: FsHttp, 14.5.0"
#r "nuget: FSharp.Json, 0.4.1"
#r "nuget: Serilog, 3.1.1"
open System
open System.Net.Http.Headers
open System.Net
open FsHttp
open FSharp.Json
open Serilog
// Omitting the stuff that we don't actually use
type RedditFeedRootJson =
{ data:
{| after: string option
children:
{| data:
{| id: string
title: string
permalink: string
subreddit: string
score: int64
created_utc: float
url: string
thumbnail: string
is_self: bool
media: {| oembed: {| ``type``: string option |} option |} option
post_hint: string option
is_video: bool |} |}[] |} }
type HttpStatusAndHeaders =
{ headers: HttpResponseHeaders
statusCode: HttpStatusCode }
[<Serializable>]
exception TooManyRequestsError of HttpStatusAndHeaders
[<Serializable>]
exception Non200ResponseError of HttpStatusAndHeaders
let isNon200HttpResponse (code: HttpStatusCode) = int (code) < 200 || int (code) >= 300
let ridoVersion = 0.1
let fetchFeedData (redditFeedToGet: string, oauthBearerToken) =
task {
try
let! resp =
http {
GET "https://oauth.reddit.com/r/aww/.json"
config_timeoutInSeconds 30
// dotnet doesn't like colons in the user agent
UserAgent $"dotnet RIDO v{ridoVersion} (by github.com/Darkle/RIDO)"
AuthorizationBearer oauthBearerToken
}
|> Request.sendAsync
if HttpStatusCode.TooManyRequests.Equals(resp.statusCode) then
raise (
TooManyRequestsError(
{ headers = resp.headers
statusCode = resp.statusCode }
)
)
else if isNon200HttpResponse (resp.statusCode) then
raise (
Non200ResponseError(
{ headers = resp.headers
statusCode = resp.statusCode }
)
)
let! data = resp |> Response.toTextAsync
// Using FSharp.Json here as it will auto convert null values to optionals
return data |> Json.deserialize<RedditFeedRootJson> |> Ok
with e ->
return Error(e)
}
let getResetHeader (headers: HttpResponseHeaders) =
// TODO
()
let foo (feedUrl, oauthBearerToken) =
task {
let! fetchFeedResult = fetchFeedData (feedUrl, oauthBearerToken)
match fetchFeedResult with
| Error err ->
match err with
| :? TooManyRequestsError as response ->
Log.Information("service: {service}. error: {error}", "feed-updates", err)
let secondsTillRateLimitReset = getResetHeader (response.Data0.headers)
//... do backoff here
| _ ->
Log.Error(
"service: {service}. message: Error fetching data for: {feedUrl}. error: {@error}",
"feed-updates",
feedUrl,
err
)
| Ok feedData ->
Log.Information("service: {service}. Successfully fetched data for: {feedUrl}", "feed-updates", feedUrl)
//... do other stuff
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment