diff --git a/README.md b/README.md index 2856ff9..22aee5e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ is on implementing missing features. - Search (images/videos, hashtags, etc.) - Custom timeline filter -- Media carousel below profile - Media-only/gallery view - Nitter link previews - Server configuration diff --git a/public/style.css b/public/style.css index 127dcae..f407c58 100644 --- a/public/style.css +++ b/public/style.css @@ -477,9 +477,13 @@ video { .profile-bio { overflow: hidden; - margin-right: -6px; overflow-wrap: break-word; width: 100%; + margin: 10px -6px 0px 0px; +} + +.profile-bio p { + margin: 0; } .profile-description { @@ -490,6 +494,47 @@ video { word-wrap: break-word; } +.photo-rail-card { + float: left; + background: #161616; + border-radius: 0 0 4px 4px; + width: 100%; + margin-top: 5px; +} + +.photo-rail-heading { + padding: 5px 12px 0px 12px; +} + +.photo-rail-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 3px 3px; + padding: 5px 12px 12px 12px; +} + +.photo-rail-grid a { + position: relative; + border-radius: 5px; +} + +.photo-rail-grid a:before { + content: ""; + display: block; + padding-top: 100%; +} + +.photo-rail-grid img { + height: 100%; + width: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; +} + .tab { align-items: center; display: flex; diff --git a/screenshot.png b/screenshot.png index 6b29dce..f922001 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/src/api.nim b/src/api.nim index fbd4332..7f1cb9f 100644 --- a/src/api.nim +++ b/src/api.nim @@ -15,6 +15,7 @@ const timelineUrl = "i/profiles/show/$1/timeline/tweets" timelineSearchUrl = "i/search/timeline" + timelineMediaUrl = "i/profiles/show/$1/media_timeline" profilePopupUrl = "i/profiles/popup" profileIntentUrl = "intent/user" tweetUrl = "status" @@ -162,6 +163,24 @@ proc getConversationPolls*(convo: Conversation) {.async.} = futs.add convo.replies.map(getPolls) await all(futs) +proc getPhotoRail*(username: string): Future[seq[GalleryPhoto]] {.async.} = + let headers = newHttpHeaders({ + "Accept": jsonAccept, + "Referer": $(base / username), + "User-Agent": agent, + "X-Requested-With": "XMLHttpRequest" + }) + + let params = { + "for_photo_rail": "true", + "oldest_unread_id": "0" + } + + let url = base / (timelineMediaUrl % username) ? params + let html = await fetchHtml(url, headers, jsonKey="items_html") + + result = parsePhotoRail(html) + proc getProfileFallback(username: string; headers: HttpHeaders): Future[Profile] {.async.} = let url = base / profileIntentUrl ? {"screen_name": username} let html = await fetchHtml(url, headers) diff --git a/src/nitter.nim b/src/nitter.nim index 11e5d11..6f79fa7 100644 --- a/src/nitter.nim +++ b/src/nitter.nim @@ -12,10 +12,11 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a let username = name.strip(chars={'/'}) profileFut = getCachedProfile(username) + railFut = getPhotoRail(username) var timelineFut: Future[Timeline] if query.isNone: - timelineFut = getTimeline(username, after) + timelineFut = getTimeline(username, after) else: timelineFut = getTimelineSearch(username, after, get(query)) @@ -23,7 +24,7 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a if profile.username.len == 0: return "" - let profileHtml = renderProfile(profile, await timelineFut, after.len == 0) + let profileHtml = renderProfile(profile, await timelineFut, await railFut, after.len == 0) return renderMain(profileHtml, title=pageTitle(profile)) template respTimeline(timeline: typed) = diff --git a/src/parser.nim b/src/parser.nim index f2b51c9..b57b4f7 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -163,3 +163,11 @@ proc parsePoll*(node: XmlNode): Poll = if n > highest: highest = n result.leader = i + +proc parsePhotoRail*(node: XmlNode): seq[GalleryPhoto] = + for img in node.selectAll(".tweet-media-img-placeholder"): + result.add GalleryPhoto( + url: img.attr("data-image-url"), + tweetId: img.attr("data-tweet-id"), + color: img.attr("background-color").replace("style", "background-color") + ) diff --git a/src/types.nim b/src/types.nim index 575172e..710dd62 100644 --- a/src/types.nim +++ b/src/types.nim @@ -57,6 +57,11 @@ type url*: string thumb*: string + GalleryPhoto* = object + url*: string + tweetId*: string + color*: string + Poll* = object options*: seq[string] values*: seq[int] diff --git a/src/views/user.nimf b/src/views/user.nimf index e460d1c..4d66812 100644 --- a/src/views/user.nimf +++ b/src/views/user.nimf @@ -41,6 +41,23 @@ #end proc # +#proc renderPhotoRail(username: string; photoRail: seq[GalleryPhoto]): string = +
+
+ 🖼 Photos and videos +
+
+ #for i, photo in photoRail: + #if i == 20: break + #end if + + + + #end for +
+
+#end proc +# #proc renderBanner(profile: Profile): string = #if "#" in profile.banner:
@@ -90,13 +107,17 @@ #end proc # -#proc renderProfile*(profile: Profile; timeline: Timeline; beginning: bool): string = +#proc renderProfile*(profile: Profile; timeline: Timeline; +# photoRail: seq[GalleryPhoto]; beginning: bool): string =
${renderBanner(profile)}
${renderProfileCard(profile)} + #if photoRail.len > 0: + ${renderPhotoRail(profile.username, photoRail)} + #end if
#let link = "/" & profile.username