Sfoglia il codice sorgente

Add multi-user timeline support

Zed 7 anni fa
parent
commit
eeead99e32
8 ha cambiato i file con 80 aggiunte e 35 eliminazioni
  1. 19 2
      public/style.css
  2. 1 1
      src/api.nim
  3. 24 8
      src/nitter.nim
  4. 7 5
      src/search.nim
  5. 2 2
      src/types.nim
  6. 1 1
      src/views/general.nim
  7. 6 1
      src/views/profile.nim
  8. 20 15
      src/views/timeline.nim

+ 19 - 2
public/style.css

@@ -140,7 +140,7 @@ a:hover {
 
 .replying-to {
     color: hsla(240,1%,73%,.9);
-    margin: 4px 0;
+    margin: -4px 0 4px 0;
 }
 
 .status-el .status-content {
@@ -369,6 +369,20 @@ video {
     background-color: #282828;
 }
 
+.multi-header {
+    background-color: #161616;
+    text-align: center;
+    padding: 10px;
+    display: block;
+    font-weight: bold;
+    margin-bottom: 5px;
+}
+
+.multi-timeline {
+    max-width: 600px;
+    margin: 0 auto;
+}
+
 .profile-tabs {
     max-width: 900px;
     margin: 0 auto;
@@ -387,6 +401,9 @@ video {
     margin: 0;
     text-align: left;
     vertical-align: top;
+}
+
+.profile-tabs > .timeline-tab {
     width: 68% !important;
 }
 
@@ -756,7 +773,7 @@ video {
 }
 
 .timeline-protected {
-    padding-left: 12px;
+    text-align: center;
 }
 
 .timeline-protected p {

+ 1 - 1
src/api.nim

@@ -308,7 +308,7 @@ proc getTimeline*(username, after, agent: string): Future[Timeline] {.async.} =
   let json = await fetchJson(base / (timelineUrl % username) ? params, headers)
   result = await finishTimeline(json, none(Query), after, agent)
 
-proc getTimelineSearch*(username, after, agent: string; query: Query): Future[Timeline] {.async.} =
+proc getTimelineSearch*(query: Query; after, agent: string): Future[Timeline] {.async.} =
   let queryParam = genQueryParam(query)
   let queryEncoded = encodeUrl(queryParam, usePlus=false)
 

+ 24 - 8
src/nitter.nim

@@ -9,18 +9,15 @@ import views/[general, profile, status]
 const configPath {.strdefine.} = "./nitter.conf"
 let cfg = getConfig(configPath)
 
-proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.async.} =
-  let
-    agent = getAgent()
-    username = name.strip(chars={'/'})
-    profileFut = getCachedProfile(username, agent)
-    railFut = getPhotoRail(username, agent)
+proc showSingleTimeline(name, after, agent: string; query: Option[Query]): Future[string] {.async.} =
+  let profileFut = getCachedProfile(name, agent)
+  let railFut = getPhotoRail(name, agent)
 
   var timelineFut: Future[Timeline]
   if query.isNone:
-    timelineFut = getTimeline(username, after, agent)
+    timelineFut = getTimeline(name, after, agent)
   else:
-    timelineFut = getTimelineSearch(username, after, agent, get(query))
+    timelineFut = getTimelineSearch(get(query), after, agent)
 
   let profile = await profileFut
   if profile.username.len == 0:
@@ -29,6 +26,25 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a
   let profileHtml = renderProfile(profile, await timelineFut, await railFut)
   return renderMain(profileHtml, title=cfg.title, titleText=pageTitle(profile))
 
+proc showMultiTimeline(names: seq[string]; after, agent: string; query: Option[Query]): Future[string] {.async.} =
+  var q = query
+  if q.isSome:
+    get(q).fromUser = names
+  else:
+    q = some(Query(kind: multi, fromUser: names, excludes: @["replies"]))
+
+  var timeline = renderMulti(await getTimelineSearch(get(q), after, agent), names.join(","))
+  return renderMain(timeline, title=cfg.title, titleText=names.join(" | "))
+
+proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.async.} =
+  let agent = getAgent()
+  let names = name.strip(chars={'/'}).split(",")
+
+  if names.len == 1:
+    return await showSingleTimeline(names[0], after, agent, query)
+  else:
+    return await showMultiTimeline(names, after, agent, query)
+
 template respTimeline(timeline: typed) =
   if timeline.len == 0:
     resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)

+ 7 - 5
src/search.nim

@@ -25,7 +25,7 @@ proc initQuery*(filters, includes, excludes, separator: string; name=""): Query
     filters: filters.split(",").filterIt(it in validFilters),
     includes: includes.split(",").filterIt(it in validFilters),
     excludes: excludes.split(",").filterIt(it in validFilters),
-    fromUser: name,
+    fromUser: @[name],
     sep: if sep in separators: sep else: ""
   )
 
@@ -33,7 +33,7 @@ proc getMediaQuery*(name: string): Query =
   Query(
     kind: media,
     filters: @["twimg", "native_video"],
-    fromUser: name,
+    fromUser: @[name],
     sep: "OR"
   )
 
@@ -41,15 +41,17 @@ proc getReplyQuery*(name: string): Query =
   Query(
     kind: replies,
     includes: @["nativeretweets"],
-    fromUser: name
+    fromUser: @[name]
   )
 
 proc genQueryParam*(query: Query): string =
   var filters: seq[string]
   var param: string
 
-  if query.fromUser.len > 0:
-    param = &"from:{query.fromUser} "
+  for i, user in query.fromUser:
+    param &= &"from:{user} "
+    if i < query.fromUser.high:
+      param &= "OR "
 
   for f in query.filters:
     filters.add "filter:" & f

+ 2 - 2
src/types.nim

@@ -32,14 +32,14 @@ db("cache.db", "", "", ""):
 
 type
   QueryKind* = enum
-    replies, media, custom = "search"
+    replies, media, multi, custom = "search"
 
   Query* = object
     kind*: QueryKind
     filters*: seq[string]
     includes*: seq[string]
     excludes*: seq[string]
-    fromUser*: string
+    fromUser*: seq[string]
     sep*: string
 
   VideoType* = enum

+ 1 - 1
src/views/general.nim

@@ -26,7 +26,7 @@ proc renderSearch*(): VNode =
   buildHtml(tdiv(class="panel")):
     tdiv(class="search-panel"):
       form(`method`="post", action="search"):
-        input(`type`="text", name="query", placeholder="Enter username...")
+        input(`type`="text", name="query", placeholder="Enter usernames...")
         button(`type`="submit"): text "🔎"
 
 proc renderError*(error: string): VNode =

+ 6 - 1
src/views/profile.nim

@@ -64,4 +64,9 @@ proc renderProfile*(profile: Profile; timeline: Timeline;
         renderPhotoRail(profile.username, photoRail)
 
     tdiv(class="timeline-tab"):
-      renderTimeline(timeline, profile)
+      renderTimeline(timeline, profile.username, profile.protected)
+
+proc renderMulti*(timeline: Timeline; usernames: string): VNode =
+  buildHtml(tdiv(class="multi-timeline")):
+    tdiv(class="timeline-tab"):
+      renderTimeline(timeline, usernames, false, multi=true)

+ 20 - 15
src/views/timeline.nim

@@ -11,16 +11,16 @@ proc getQuery(timeline: Timeline): string =
 proc getTabClass(timeline: Timeline; tab: string): string =
   var classes = @["tab-item"]
 
-  if timeline.query.isNone:
+  if timeline.query.isNone or get(timeline.query).kind == multi:
     if tab == "posts":
       classes.add "active"
-  elif $timeline.query.get().kind == tab:
+  elif $get(timeline.query).kind == tab:
     classes.add "active"
 
   return classes.join(" ")
 
-proc renderSearchTabs(timeline: Timeline; profile: Profile): VNode =
-  let link = "/" & profile.username
+proc renderSearchTabs(timeline: Timeline; username: string): VNode =
+  let link = "/" & username
   buildHtml(ul(class="tab")):
     li(class=timeline.getTabClass("posts")):
       a(href=link): text "Tweets"
@@ -29,14 +29,14 @@ proc renderSearchTabs(timeline: Timeline; profile: Profile): VNode =
     li(class=timeline.getTabClass("media")):
       a(href=(link & "/media")): text "Media"
 
-proc renderNewer(timeline: Timeline; profile: Profile): VNode =
+proc renderNewer(timeline: Timeline; username: string): VNode =
   buildHtml(tdiv(class="status-el show-more")):
-    a(href=("/" & profile.username & getQuery(timeline).strip(chars={'?'}))):
+    a(href=("/" & username & getQuery(timeline).strip(chars={'?'}))):
       text "Load newest tweets"
 
-proc renderOlder(timeline: Timeline; profile: Profile): VNode =
+proc renderOlder(timeline: Timeline; username: string): VNode =
   buildHtml(tdiv(class="show-more")):
-    a(href=(&"/{profile.username}{getQuery(timeline)}after={timeline.minId}")):
+    a(href=(&"/{username}{getQuery(timeline)}after={timeline.minId}")):
       text "Load older tweets"
 
 proc renderNoMore(): VNode =
@@ -74,20 +74,25 @@ proc renderTweets(timeline: Timeline): VNode =
         renderThread(thread)
         threads &= tweet.threadId
 
-proc renderTimeline*(timeline: Timeline; profile: Profile): VNode =
+proc renderTimeline*(timeline: Timeline; username: string;
+                     protected: bool; multi=false): VNode =
   buildHtml(tdiv):
-    renderSearchTabs(timeline, profile)
+    if multi:
+      tdiv(class="multi-header"):
+        text username.replace(",", " | ")
 
-    if not profile.protected and not timeline.beginning:
-      renderNewer(timeline, profile)
+    if not protected:
+      renderSearchTabs(timeline, username)
+      if not timeline.beginning:
+        renderNewer(timeline, username)
 
-    if profile.protected:
-      renderProtected(profile.username)
+    if protected:
+      renderProtected(username)
     elif timeline.tweets.len == 0:
       renderNoneFound()
     else:
       renderTweets(timeline)
       if timeline.hasMore or timeline.query.isSome:
-        renderOlder(timeline, profile)
+        renderOlder(timeline, username)
       else:
         renderNoMore()