rss.nim 4.6 KB

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