rss.nim 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # SPDX-License-Identifier: AGPL-3.0-only
  2. import asyncdispatch, strutils, tables, times, hashes, uri
  3. import jester, supersnappy
  4. import router_utils, timeline
  5. import ../query
  6. include "../views/rss.nimf"
  7. export times, hashes, supersnappy
  8. proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async.} =
  9. var profile: Profile
  10. var timeline: Timeline
  11. let
  12. name = req.params.getOrDefault("name")
  13. after = getCursor(req)
  14. names = getNames(name)
  15. if names.len == 1:
  16. (profile, timeline) =
  17. await fetchSingleTimeline(after, query, skipRail=true)
  18. else:
  19. var q = query
  20. q.fromUser = names
  21. timeline = await getSearch[Tweet](q, after)
  22. # this is kinda dumb
  23. profile = Profile(
  24. username: name,
  25. fullname: names.join(" | "),
  26. userpic: "https://abs.twimg.com/sticky/default_profile_images/default_profile.png"
  27. )
  28. if profile.suspended:
  29. return Rss(feed: profile.username, cursor: "suspended")
  30. if profile.fullname.len > 0:
  31. let rss = compress renderTimelineRss(timeline, profile, cfg,
  32. multi=(names.len > 1))
  33. return Rss(feed: rss, cursor: timeline.bottom)
  34. template respRss*(rss, page) =
  35. if rss.cursor.len == 0:
  36. let info = case page
  37. of "User": " \"$1\" " % @"name"
  38. of "List": " $1 " % @"id"
  39. else: " "
  40. resp Http404, showError(page & info & "not found", cfg)
  41. elif rss.cursor.len == 9 and rss.cursor == "suspended":
  42. resp Http404, showError(getSuspended(rss.feed), cfg)
  43. let headers = {"Content-Type": "application/rss+xml; charset=utf-8",
  44. "Min-Id": rss.cursor}
  45. resp Http200, headers, uncompress rss.feed
  46. proc createRssRouter*(cfg: Config) =
  47. router rss:
  48. get "/search/rss":
  49. cond cfg.enableRss
  50. if @"q".len > 200:
  51. resp Http400, showError("Search input too long.", cfg)
  52. let query = initQuery(params(request))
  53. if query.kind != tweets:
  54. resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg)
  55. let
  56. cursor = getCursor()
  57. key = "search:" & $hash(genQueryUrl(query)) & ":" & cursor
  58. var rss = await getCachedRss(key)
  59. if rss.cursor.len > 0:
  60. respRss(rss, "Search")
  61. let tweets = await getSearch[Tweet](query, cursor)
  62. rss.cursor = tweets.bottom
  63. rss.feed = compress renderSearchRss(tweets.content, query.text,
  64. genQueryUrl(query), cfg)
  65. await cacheRss(key, rss)
  66. respRss(rss, "Search")
  67. get "/@name/rss":
  68. cond cfg.enableRss
  69. cond '.' notin @"name"
  70. let
  71. cursor = getCursor()
  72. name = @"name"
  73. key = "twitter:" & name & ":" & cursor
  74. var rss = await getCachedRss(key)
  75. if rss.cursor.len > 0:
  76. respRss(rss, "User")
  77. rss = await timelineRss(request, cfg, Query(fromUser: @[name]))
  78. await cacheRss(key, rss)
  79. respRss(rss, "User")
  80. get "/@name/@tab/rss":
  81. cond cfg.enableRss
  82. cond '.' notin @"name"
  83. cond @"tab" in ["with_replies", "media", "search"]
  84. let name = @"name"
  85. let query =
  86. case @"tab"
  87. of "with_replies": getReplyQuery(name)
  88. of "media": getMediaQuery(name)
  89. of "search": initQuery(params(request), name=name)
  90. else: Query(fromUser: @[name])
  91. var key = @"tab" & ":" & @"name" & ":"
  92. if @"tab" == "search":
  93. key &= $hash(genQueryUrl(query)) & ":"
  94. key &= getCursor()
  95. var rss = await getCachedRss(key)
  96. if rss.cursor.len > 0:
  97. respRss(rss, "User")
  98. rss = await timelineRss(request, cfg, query)
  99. await cacheRss(key, rss)
  100. respRss(rss, "User")
  101. get "/@name/lists/@slug/rss":
  102. cond cfg.enableRss
  103. cond @"name" != "i"
  104. let
  105. slug = decodeUrl(@"slug")
  106. list = await getCachedList(@"name", slug)
  107. cursor = getCursor()
  108. if list.id.len == 0:
  109. resp Http404, showError("List \"" & @"slug" & "\" not found", cfg)
  110. let url = "/i/lists/" & list.id & "/rss"
  111. if cursor.len > 0:
  112. redirect(url & "?cursor=" & encodeUrl(cursor, false))
  113. else:
  114. redirect(url)
  115. get "/i/lists/@id/rss":
  116. cond cfg.enableRss
  117. let
  118. cursor = getCursor()
  119. key =
  120. if cursor.len == 0: "lists:" & @"id"
  121. else: "lists:" & @"id" & ":" & cursor
  122. var rss = await getCachedRss(key)
  123. if rss.cursor.len > 0:
  124. respRss(rss, "List")
  125. let
  126. list = await getCachedList(id=(@"id"))
  127. timeline = await getListTimeline(list.id, cursor)
  128. rss.cursor = timeline.bottom
  129. rss.feed = compress renderListRss(timeline.content, list, cfg)
  130. await cacheRss(key, rss)
  131. respRss(rss, "List")