diff --git a/src/apiutils.nim b/src/apiutils.nim index cebe3af..89c7687 100644 --- a/src/apiutils.nim +++ b/src/apiutils.nim @@ -31,13 +31,16 @@ proc genHeaders*(token: Token = nil): HttpHeaders = "DNT": "1" }) +proc rateLimitError(): ref RateLimitError = + newException(RateLimitError, "rate limited with " & getPoolInfo()) + proc fetch*(url: Uri; oldApi=false): Future[JsonNode] {.async.} = once: pool = HttpPool() var token = await getToken() if token.tok.len == 0: - result = newJNull() + raise rateLimitError() let headers = genHeaders(token) try: @@ -59,4 +62,4 @@ proc fetch*(url: Uri; oldApi=false): Future[JsonNode] {.async.} = token.release() except Exception: echo "error: ", url - result = newJNull() + raise rateLimitError() diff --git a/src/nitter.nim b/src/nitter.nim index 11951b1..586cd4e 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -1,5 +1,6 @@ import asyncdispatch, strformat from net import Port +from htmlgen import a import jester @@ -9,6 +10,8 @@ import routes/[ preferences, timeline, status, media, search, rss, list, unsupported, embed, resolver, router_utils] +const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances" + const configPath {.strdefine.} = "./nitter.conf" let (cfg, fullCfg) = getConfig(configPath) @@ -71,6 +74,12 @@ routes: error Http404: resp Http404, showError("Page not found", cfg) + error RateLimitError: + echo error.exc.msg + resp Http429, showError("Instance has been rate limited.
Use " & + a("another instance", href = instancesUrl) & + " or try again later.", cfg) + extend unsupported, "" extend preferences, "" extend resolver, "" diff --git a/src/sass/general.scss b/src/sass/general.scss index 36a2e4a..9feb3d3 100644 --- a/src/sass/general.scss +++ b/src/sass/general.scss @@ -8,6 +8,7 @@ .error-panel { @include center-panel(var(--error_red)); + text-align: center; } .search-bar > form { diff --git a/src/tokens.nim b/src/tokens.nim index 3a85ac7..5741b9d 100644 --- a/src/tokens.nim +++ b/src/tokens.nim @@ -1,4 +1,5 @@ -import asyncdispatch, httpclient, times, sequtils, strutils, json +import asyncdispatch, httpclient, times, sequtils, json, math +import strutils, strformat import types, agents, consts, http_pool var @@ -32,8 +33,8 @@ proc fetchToken(): Future[Token] {.async.} = init: time, lastUse: time) except Exception as e: lastFailed = getTime() - echo "fetching token failed: ", e.msg result = Token() + echo "fetching token failed: ", e.msg proc expired(token: Token): bool {.inline.} = const @@ -77,3 +78,7 @@ proc initTokenPool*(cfg: Config) {.async.} = if tokenPool.countIt(not it.isLimited) < cfg.minTokens: await poolTokens(min(4, cfg.minTokens - tokenPool.len)) await sleepAsync(2000) + +proc getPoolInfo*: string = + let avg = tokenPool.mapIt(it.remaining).sum() + return &"{tokenPool.len} tokens, average remaining: {avg}" diff --git a/src/types.nim b/src/types.nim index dbfe1f6..756b277 100644 --- a/src/types.nim +++ b/src/types.nim @@ -4,6 +4,8 @@ import prefs_impl genPrefsType() type + RateLimitError* = object of CatchableError + Token* = ref object tok*: string remaining*: int diff --git a/src/views/general.nim b/src/views/general.nim index 742b6ea..267722a 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -117,4 +117,4 @@ proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs; proc renderError*(error: string): VNode = buildHtml(tdiv(class="panel-container")): tdiv(class="error-panel"): - span: text error + span: verbatim error