nitter.nim 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import asyncdispatch, asyncfile, httpclient, strutils, strformat, uri, os
  2. import jester
  3. import api, utils, types, cache, formatters
  4. include views/"user.nimf"
  5. include views/"general.nimf"
  6. const cacheDir {.strdefine.} = "/tmp/nitter"
  7. proc showTimeline(name: string; num=""): Future[string] {.async.} =
  8. let
  9. username = name.strip(chars={'/'})
  10. profileFut = getCachedProfile(username)
  11. tweetsFut = getTimeline(username, after=num)
  12. let profile = await profileFut
  13. if profile.username.len == 0:
  14. return ""
  15. let profileHtml = renderProfile(profile, await tweetsFut, num.len == 0)
  16. return renderMain(profileHtml, title=pageTitle(profile))
  17. routes:
  18. get "/":
  19. resp renderMain(renderSearchPanel(), title=pageTitle("Search"))
  20. post "/search":
  21. if @"query".len == 0:
  22. resp Http404, showError("Please enter a username.")
  23. redirect("/" & @"query")
  24. get "/@name/?":
  25. cond '.' notin @"name"
  26. let timeline = await showTimeline(@"name", @"after")
  27. if timeline.len == 0:
  28. resp Http404, showError("User \"" & @"name" & "\" not found")
  29. resp timeline
  30. get "/@name/status/@id":
  31. cond '.' notin @"name"
  32. let conversation = await getTweet(@"name", @"id")
  33. if conversation.isNil or conversation.tweet.id.len == 0:
  34. resp Http404, showError("Tweet not found")
  35. let title = pageTitle(conversation.tweet.profile)
  36. resp renderMain(renderConversation(conversation), title=title)
  37. get "/pic/@sig/@url":
  38. cond "http" in @"url"
  39. cond "twimg" in @"url"
  40. let
  41. uri = parseUri(decodeUrl(@"url"))
  42. path = uri.path.split("/")[2 .. ^1].join("/")
  43. filename = cacheDir / cleanFilename(path & uri.query)
  44. if getHmac($uri) != @"sig":
  45. resp showError("Failed to verify signature")
  46. if not existsDir(cacheDir):
  47. createDir(cacheDir)
  48. if not existsFile(filename):
  49. let client = newAsyncHttpClient()
  50. await client.downloadFile($uri, filename)
  51. client.close()
  52. if not existsFile(filename):
  53. resp Http404
  54. let file = openAsync(filename)
  55. defer: file.close()
  56. resp await readAll(file), mimetype(filename)
  57. get "/video/@sig/@url":
  58. cond "http" in @"url"
  59. cond "video.twimg" in @"url"
  60. let url = decodeUrl(@"url")
  61. if getHmac(url) != @"sig":
  62. resp showError("Failed to verify signature")
  63. let
  64. client = newAsyncHttpClient()
  65. video = await client.getContent(url)
  66. defer: client.close()
  67. resp video, mimetype(url)
  68. runForever()