Просмотр исходного кода

Use media endpoint for profile media tab

This bypasses "search" rate limits. It now includes media beyond
images and videos (eg. YouTube links are "media"), but the old
behaviour can be restored by clicking search, then filtering "Media"
and excluding retweets and replies.
Zed 6 лет назад
Родитель
Сommit
ffce6e21ab
6 измененных файлов с 55 добавлено и 28 удалено
  1. 1 4
      src/api/list.nim
  2. 3 2
      src/api/media.nim
  3. 30 13
      src/api/timeline.nim
  4. 9 0
      src/api/utils.nim
  5. 1 1
      src/routes/rss.nim
  6. 11 8
      src/routes/timeline.nim

+ 1 - 4
src/api/list.nim

@@ -21,10 +21,7 @@ proc getListTimeline*(username, list, agent, after: string; media=true): Future[
   if result.content.len == 0:
   if result.content.len == 0:
     return
     return
 
 
-  let last = result.content[^1]
-  result.minId =
-    if last.retweet.isNone: $last.id
-    else: $(get(last.retweet).id)
+  result.minId = getLastId(result)
 
 
 proc getListMembers*(username, list, agent: string): Future[Result[Profile]] {.async.} =
 proc getListMembers*(username, list, agent: string): Future[Result[Profile]] {.async.} =
   let
   let

+ 3 - 2
src/api/media.nim

@@ -123,9 +123,10 @@ proc getCard*(tweet: Tweet; agent: string) {.async.} =
   if html == nil: return
   if html == nil: return
   parseCard(get(tweet.card), html)
   parseCard(get(tweet.card), html)
 
 
-proc getPhotoRail*(username, agent: string): Future[seq[GalleryPhoto]] {.async.} =
+proc getPhotoRail*(username, agent: string; skip=false): Future[seq[GalleryPhoto]] {.async.} =
+  if skip: return
   let
   let
-    headers = genHeaders({"x-requested-with": "XMLHttpRequest"}, agent, base / username)
+    headers = genHeaders(agent, base / username, xml=true)
     params = {"for_photo_rail": "true", "oldest_unread_id": "0"}
     params = {"for_photo_rail": "true", "oldest_unread_id": "0"}
     url = base / (timelineMediaUrl % username) ? params
     url = base / (timelineMediaUrl % username) ? params
     html = await fetchHtml(url, headers, jsonKey="items_html")
     html = await fetchHtml(url, headers, jsonKey="items_html")

+ 30 - 13
src/api/timeline.nim

@@ -18,10 +18,24 @@ proc finishTimeline*(json: JsonNode; query: Query; after, agent: string;
   if not json.hasKey("items_html"): return
   if not json.hasKey("items_html"): return
 
 
   let html = parseHtml(json["items_html"].to(string))
   let html = parseHtml(json["items_html"].to(string))
-  let thread = parseChain(html)
+  let timeline = parseChain(html)
 
 
-  if media: await getMedia(thread, agent)
-  result.content = thread.content
+  if media: await getMedia(timeline, agent)
+  result.content = timeline.content
+
+proc getProfileAndTimeline*(username, agent, after: string; media=true): Future[(Profile, Timeline)] {.async.} =
+  var url = base / username
+  if after.len > 0:
+    url = url ? {"max_position": after}
+
+  let
+    headers = genHeaders(agent, base / username, auth=true)
+    html = await fetchHtml(url, headers)
+    timeline = parseTimeline(html.select("#timeline > .stream-container"), after)
+    profile = parseTimelineProfile(html)
+
+  if media: await getMedia(timeline, agent)
+  result = (profile, timeline)
 
 
 proc getTimeline*(username, after, agent: string; media=true): Future[Timeline] {.async.} =
 proc getTimeline*(username, after, agent: string; media=true): Future[Timeline] {.async.} =
   var params = toSeq({
   var params = toSeq({
@@ -39,16 +53,19 @@ proc getTimeline*(username, after, agent: string; media=true): Future[Timeline]
 
 
   result = await finishTimeline(json, Query(), after, agent, media)
   result = await finishTimeline(json, Query(), after, agent, media)
 
 
-proc getProfileAndTimeline*(username, agent, after: string; media=true): Future[(Profile, Timeline)] {.async.} =
-  var url = base / username
+proc getMediaTimeline*(username, after, agent: string; media=true): Future[Timeline] {.async.} =
+  echo "mediaTimeline"
+  var params = toSeq({
+    "include_available_features": "1",
+    "include_entities": "1",
+    "reset_error_state": "false"
+  })
+
   if after.len > 0:
   if after.len > 0:
-    url = url ? {"max_position": after}
+    params.add {"max_position": after}
 
 
-  let
-    headers = genHeaders(agent, base / username, auth=true)
-    html = await fetchHtml(url, headers)
-    timeline = parseTimeline(html.select("#timeline > .stream-container"), after)
-    profile = parseTimelineProfile(html)
+  let headers = genHeaders(agent, base / username, xml=true)
+  let json = await fetchJson(base / (timelineMediaUrl % username) ? params, headers)
 
 
-  if media: await getMedia(timeline, agent)
-  result = (profile, timeline)
+  result = await finishTimeline(json, Query(kind: QueryKind.media), after, agent, media)
+  result.minId = getLastId(result)

+ 9 - 0
src/api/utils.nim

@@ -1,6 +1,7 @@
 import httpclient, asyncdispatch, htmlparser
 import httpclient, asyncdispatch, htmlparser
 import strutils, json, xmltree, uri
 import strutils, json, xmltree, uri
 
 
+import ../types
 import consts
 import consts
 
 
 proc genHeaders*(headers: openArray[tuple[key: string, val: string]];
 proc genHeaders*(headers: openArray[tuple[key: string, val: string]];
@@ -52,3 +53,11 @@ proc fetchJson*(url: Uri; headers: HttpHeaders): Future[JsonNode] {.async.} =
     result = parseJson(resp)
     result = parseJson(resp)
   except:
   except:
     return nil
     return nil
+
+proc getLastId*(tweets: Result[Tweet]): string =
+  if tweets.content.len == 0: return
+  let last = tweets.content[^1]
+  if last.retweet.isNone:
+    $last.id
+  else:
+    $(get(last.retweet).id)

+ 1 - 1
src/routes/rss.nim

@@ -9,7 +9,7 @@ import ../views/general
 include "../views/rss.nimf"
 include "../views/rss.nimf"
 
 
 proc showRss*(name, hostname: string; query: Query): Future[string] {.async.} =
 proc showRss*(name, hostname: string; query: Query): Future[string] {.async.} =
-  let (profile, timeline, _) =
+  let (profile, timeline) =
     await fetchSingleTimeline(name, "", getAgent(), query, media=false)
     await fetchSingleTimeline(name, "", getAgent(), query, media=false)
 
 
   if timeline != nil:
   if timeline != nil:

+ 11 - 8
src/routes/timeline.nim

@@ -11,12 +11,8 @@ export router_utils
 export api, cache, formatters, query, agents
 export api, cache, formatters, query, agents
 export profile, timeline, status
 export profile, timeline, status
 
 
-type ProfileTimeline = (Profile, Timeline, seq[GalleryPhoto])
-
 proc fetchSingleTimeline*(name, after, agent: string; query: Query;
 proc fetchSingleTimeline*(name, after, agent: string; query: Query;
-                          media=true): Future[ProfileTimeline] {.async.} =
-  let railFut = getPhotoRail(name, agent)
-
+                          media=true): Future[(Profile, Timeline)] {.async.} =
   var timeline: Timeline
   var timeline: Timeline
   var profile: Profile
   var profile: Profile
   var cachedProfile = hasCachedProfile(name)
   var cachedProfile = hasCachedProfile(name)
@@ -31,13 +27,17 @@ proc fetchSingleTimeline*(name, after, agent: string; query: Query;
       (profile, timeline) = await getProfileAndTimeline(name, agent, after, media)
       (profile, timeline) = await getProfileAndTimeline(name, agent, after, media)
       cache(profile)
       cache(profile)
   else:
   else:
-    var timelineFut = getSearch[Tweet](query, after, agent, media)
+    var timelineFut =
+      if query.kind == QueryKind.media:
+        getMediaTimeline(name, after, agent, media)
+      else:
+        getSearch[Tweet](query, after, agent, media)
     if cachedProfile.isNone:
     if cachedProfile.isNone:
       profile = await getCachedProfile(name, agent)
       profile = await getCachedProfile(name, agent)
     timeline = await timelineFut
     timeline = await timelineFut
 
 
   if profile.username.len == 0: return
   if profile.username.len == 0: return
-  return (profile, timeline, await railFut)
+  return (profile, timeline)
 
 
 proc fetchMultiTimeline*(names: seq[string]; after, agent: string;
 proc fetchMultiTimeline*(names: seq[string]; after, agent: string;
                          query: Query): Future[Timeline] {.async.} =
                          query: Query): Future[Timeline] {.async.} =
@@ -60,7 +60,10 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; rss: string): Fu
     names = name.strip(chars={'/'}).split(",").filterIt(it.len > 0)
     names = name.strip(chars={'/'}).split(",").filterIt(it.len > 0)
 
 
   if names.len == 1:
   if names.len == 1:
-    let (p, t, r) = await fetchSingleTimeline(names[0], after, agent, query)
+    let
+      rail = getPhotoRail(names[0], agent, skip=(query.kind == media))
+      (p, t) = await fetchSingleTimeline(names[0], after, agent, query)
+      r = await rail
     if p.username.len == 0: return
     if p.username.len == 0: return
     let pHtml = renderProfile(p, t, r, prefs, getPath())
     let pHtml = renderProfile(p, t, r, prefs, getPath())
     return renderMain(pHtml, request, cfg, pageTitle(p), pageDesc(p),
     return renderMain(pHtml, request, cfg, pageTitle(p), pageDesc(p),