Browse Source

Add back search

Zed 3 năm trước cách đây
mục cha
commit
67203a431d
7 tập tin đã thay đổi với 78 bổ sung37 xóa
  1. 16 0
      src/api.nim
  2. 1 0
      src/consts.nim
  3. 32 2
      src/parser.nim
  4. 19 19
      src/routes/rss.nim
  5. 5 9
      src/routes/search.nim
  6. 3 5
      src/routes/timeline.nim
  7. 2 2
      src/tokens.nim

+ 16 - 0
src/api.nim

@@ -115,6 +115,22 @@ proc getGraphSearch*(query: Query; after=""): Future[Profile] {.async.} =
   result = Profile(tweets: parseGraphSearch(await fetch(url, Api.search), after))
   result.tweets.query = query
 
+proc getTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} =
+  let q = genQueryParam(query)
+  if q.len == 0 or q == emptyQuery:
+    return Timeline(query: query, beginning: true)
+
+  let url = tweetSearch ? genParams({
+    "q": q,
+    "tweet_search_mode": "live",
+    "max_id": after
+  })
+
+  result = parseTweetSearch(await fetch(url, Api.search))
+  result.query = query
+  if after.len == 0:
+    result.beginning = true
+
 proc getUserSearch*(query: Query; page="1"): Future[Result[User]] {.async.} =
   if query.text.len == 0:
     return Result[User](query: query, beginning: true)

+ 1 - 0
src/consts.nim

@@ -9,6 +9,7 @@ const
 
   photoRail* = api / "1.1/statuses/media_timeline.json"
   userSearch* = api / "1.1/users/search.json"
+  tweetSearch* = api / "1.1/search/tweets.json"
 
   graphql = api / "graphql"
   graphUser* = graphql / "u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery"

+ 32 - 2
src/parser.nim

@@ -82,12 +82,16 @@ proc parseVideo(js: JsonNode): Video =
   result = Video(
     thumb: js{"media_url_https"}.getImageStr,
     views: js{"ext", "mediaStats", "r", "ok", "viewCount"}.getStr($js{"mediaStats", "viewCount"}.getInt),
-    available: js{"ext_media_availability", "status"}.getStr.toLowerAscii == "available",
+    available: true,
     title: js{"ext_alt_text"}.getStr,
     durationMs: js{"video_info", "duration_millis"}.getInt
     # playbackType: mp4
   )
 
+  with status, js{"ext_media_availability", "status"}:
+    if status.getStr.len > 0 and status.getStr.toLowerAscii != "available":
+      result.available = false
+
   with title, js{"additional_media_info", "title"}:
     result.title = title.getStr
 
@@ -219,7 +223,9 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
   if result.hasThread and result.threadId == 0:
     result.threadId = js{"self_thread", "id_str"}.getId
 
-  if js{"is_quote_status"}.getBool:
+  if "retweeted_status" in js:
+    result.retweet = some Tweet()
+  elif js{"is_quote_status"}.getBool:
     result.quote = some Tweet(id: js{"quoted_status_id_str"}.getId)
 
   # legacy
@@ -281,6 +287,30 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
       result.text.removeSuffix(" Learn more.")
       result.available = false
 
+proc parseLegacyTweet(js: JsonNode): Tweet =
+  result = parseTweet(js, js{"card"})
+  if not result.isNil and result.available:
+    result.user = parseUser(js{"user"})
+
+    if result.quote.isSome:
+      result.quote = some parseLegacyTweet(js{"quoted_status"})
+
+proc parseTweetSearch*(js: JsonNode): Timeline =
+  if js.kind == JNull or "statuses" notin js:
+    return Timeline(beginning: true)
+
+  for tweet in js{"statuses"}:
+    let parsed = parseLegacyTweet(tweet)
+
+    if parsed.retweet.isSome:
+      parsed.retweet = some parseLegacyTweet(tweet{"retweeted_status"})
+
+    result.content.add @[parsed]
+
+  let cursor = js{"search_metadata", "next_results"}.getStr
+  if cursor.len > 0 and "max_id" in cursor:
+    result.bottom = cursor[cursor.find("=") + 1 .. cursor.find("&q=")]
+
 proc finalizeTweet(global: GlobalObjects; id: string): Tweet =
   let intId = if id.len > 0: parseBiggestInt(id) else: 0
   result = global.tweets.getOrDefault(id, Tweet(id: intId))

+ 19 - 19
src/routes/rss.nim

@@ -27,7 +27,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async.
   else:
     var q = query
     q.fromUser = names
-    profile = await getGraphSearch(q, after)
+    profile.tweets = await getTweetSearch(q, after)
     # this is kinda dumb
     profile.user = User(
       username: name,
@@ -59,29 +59,29 @@ template respRss*(rss, page) =
 
 proc createRssRouter*(cfg: Config) =
   router rss:
-    # get "/search/rss":
-    #   cond cfg.enableRss
-    #   if @"q".len > 200:
-    #     resp Http400, showError("Search input too long.", cfg)
+    get "/search/rss":
+      cond cfg.enableRss
+      if @"q".len > 200:
+        resp Http400, showError("Search input too long.", cfg)
 
-    #   let query = initQuery(params(request))
-    #   if query.kind != tweets:
-    #     resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg)
+      let query = initQuery(params(request))
+      if query.kind != tweets:
+        resp Http400, showError("Only Tweet searches are allowed for RSS feeds.", cfg)
 
-    #   let
-    #     cursor = getCursor()
-    #     key = redisKey("search", $hash(genQueryUrl(query)), cursor)
+      let
+        cursor = getCursor()
+        key = redisKey("search", $hash(genQueryUrl(query)), cursor)
 
-    #   var rss = await getCachedRss(key)
-    #   if rss.cursor.len > 0:
-    #     respRss(rss, "Search")
+      var rss = await getCachedRss(key)
+      if rss.cursor.len > 0:
+        respRss(rss, "Search")
 
-    #   let tweets = await getGraphSearch(query, cursor)
-    #   rss.cursor = tweets.bottom
-    #   rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg)
+      let tweets = await getTweetSearch(query, cursor)
+      rss.cursor = tweets.bottom
+      rss.feed = renderSearchRss(tweets.content, query.text, genQueryUrl(query), cfg)
 
-    #   await cacheRss(key, rss)
-    #   respRss(rss, "Search")
+      await cacheRss(key, rss)
+      respRss(rss, "Search")
 
     get "/@name/rss":
       cond cfg.enableRss

+ 5 - 9
src/routes/search.nim

@@ -34,15 +34,11 @@ proc createSearchRouter*(cfg: Config) =
           users = Result[User](beginning: true, query: query)
         resp renderMain(renderUserSearch(users, prefs), request, cfg, prefs, title)
       of tweets:
-        # let
-        #   tweets = await getGraphSearch(query, getCursor())
-        #   rss = "/search/rss?" & genQueryUrl(query)
-        # resp renderMain(renderTweetSearch(tweets, prefs, getPath()),
-        #                 request, cfg, prefs, title, rss=rss)
-        var fakeTimeline = Timeline(beginning: true)
-        fakeTimeline.content.add Tweet(tombstone: "Tweet search is unavailable for now")
-
-        resp renderMain(renderTweetSearch(fakeTimeline, prefs, getPath()), request, cfg, prefs, title)
+        let
+          tweets = await getTweetSearch(query, getCursor())
+          rss = "/search/rss?" & genQueryUrl(query)
+        resp renderMain(renderTweetSearch(tweets, prefs, getPath()),
+                        request, cfg, prefs, title, rss=rss)
       else:
         resp Http404, showError("Invalid search", cfg)
 

+ 3 - 5
src/routes/timeline.nim

@@ -56,8 +56,7 @@ proc fetchProfile*(after: string; query: Query; skipRail=false;
     of posts: await getGraphUserTweets(userId, TimelineKind.tweets, after)
     of replies: await getGraphUserTweets(userId, TimelineKind.replies, after)
     of media: await getGraphUserTweets(userId, TimelineKind.media, after)
-    else: Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "Tweet search is unavailable for now")]]))
-    # else: await getGraphSearch(query, after)
+    else: Profile(tweets: await getTweetSearch(query, after))
 
   result.user = await user
   result.photoRail = await rail
@@ -71,9 +70,8 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs;
                    rss, after: string): Future[string] {.async.} =
   if query.fromUser.len != 1:
     let
-      # timeline = await getGraphSearch(query, after)
-      timeline = Profile(tweets: Timeline(beginning: true, content: @[@[Tweet(tombstone: "This features is unavailable for now")]]))
-      html = renderTweetSearch(timeline.tweets, prefs, getPath())
+      timeline = await getTweetSearch(query, after)
+      html = renderTweetSearch(timeline, prefs, getPath())
     return renderMain(html, request, cfg, prefs, "Multi", rss=rss)
 
   var profile = await fetchProfile(after, query, skipPinned=prefs.hidePins)

+ 2 - 2
src/tokens.nim

@@ -41,9 +41,9 @@ proc getPoolJson*(): JsonNode =
       let
         maxReqs =
           case api
-          of Api.timeline: 180
+          of Api.timeline, Api.search: 180
           of Api.userTweets, Api.userTweetsAndReplies, Api.userRestId,
-             Api.userScreenName, Api.tweetDetail, Api.tweetResult, Api.search: 500
+             Api.userScreenName, Api.tweetDetail, Api.tweetResult: 500
           of Api.list, Api.listTweets, Api.listMembers, Api.listBySlug, Api.userMedia: 500
           of Api.userSearch: 900
         reqs = maxReqs - token.apis[api].remaining