Add photo rail support

This commit is contained in:
Zed 2019-07-04 04:18:32 +02:00
parent 080d4774cf
commit 141bfdc508
8 changed files with 103 additions and 5 deletions

View file

@ -27,7 +27,6 @@ is on implementing missing features.
- Search (images/videos, hashtags, etc.) - Search (images/videos, hashtags, etc.)
- Custom timeline filter - Custom timeline filter
- Media carousel below profile
- Media-only/gallery view - Media-only/gallery view
- Nitter link previews - Nitter link previews
- Server configuration - Server configuration

View file

@ -477,9 +477,13 @@ video {
.profile-bio { .profile-bio {
overflow: hidden; overflow: hidden;
margin-right: -6px;
overflow-wrap: break-word; overflow-wrap: break-word;
width: 100%; width: 100%;
margin: 10px -6px 0px 0px;
}
.profile-bio p {
margin: 0;
} }
.profile-description { .profile-description {
@ -490,6 +494,47 @@ video {
word-wrap: break-word; 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 { .tab {
align-items: center; align-items: center;
display: flex; display: flex;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 699 KiB

View file

@ -15,6 +15,7 @@ const
timelineUrl = "i/profiles/show/$1/timeline/tweets" timelineUrl = "i/profiles/show/$1/timeline/tweets"
timelineSearchUrl = "i/search/timeline" timelineSearchUrl = "i/search/timeline"
timelineMediaUrl = "i/profiles/show/$1/media_timeline"
profilePopupUrl = "i/profiles/popup" profilePopupUrl = "i/profiles/popup"
profileIntentUrl = "intent/user" profileIntentUrl = "intent/user"
tweetUrl = "status" tweetUrl = "status"
@ -162,6 +163,24 @@ proc getConversationPolls*(convo: Conversation) {.async.} =
futs.add convo.replies.map(getPolls) futs.add convo.replies.map(getPolls)
await all(futs) 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.} = proc getProfileFallback(username: string; headers: HttpHeaders): Future[Profile] {.async.} =
let url = base / profileIntentUrl ? {"screen_name": username} let url = base / profileIntentUrl ? {"screen_name": username}
let html = await fetchHtml(url, headers) let html = await fetchHtml(url, headers)

View file

@ -12,10 +12,11 @@ proc showTimeline(name, after: string; query: Option[Query]): Future[string] {.a
let let
username = name.strip(chars={'/'}) username = name.strip(chars={'/'})
profileFut = getCachedProfile(username) profileFut = getCachedProfile(username)
railFut = getPhotoRail(username)
var timelineFut: Future[Timeline] var timelineFut: Future[Timeline]
if query.isNone: if query.isNone:
timelineFut = getTimeline(username, after) timelineFut = getTimeline(username, after)
else: else:
timelineFut = getTimelineSearch(username, after, get(query)) 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: if profile.username.len == 0:
return "" 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)) return renderMain(profileHtml, title=pageTitle(profile))
template respTimeline(timeline: typed) = template respTimeline(timeline: typed) =

View file

@ -163,3 +163,11 @@ proc parsePoll*(node: XmlNode): Poll =
if n > highest: if n > highest:
highest = n highest = n
result.leader = i 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")
)

View file

@ -57,6 +57,11 @@ type
url*: string url*: string
thumb*: string thumb*: string
GalleryPhoto* = object
url*: string
tweetId*: string
color*: string
Poll* = object Poll* = object
options*: seq[string] options*: seq[string]
values*: seq[int] values*: seq[int]

View file

@ -41,6 +41,23 @@
</div> </div>
#end proc #end proc
# #
#proc renderPhotoRail(username: string; photoRail: seq[GalleryPhoto]): string =
<div class="photo-rail-card">
<div class="photo-rail-heading">
<a href="/${username}/media">🖼 Photos and videos</a>
</div>
<div class="photo-rail-grid">
#for i, photo in photoRail:
#if i == 20: break
#end if
<a href="/${username}/status/${photo.tweetId}" style="${photo.color}">
<img src=${getSigUrl(photo.url & ":thumb", "pic")}></img>
</a>
#end for
</div>
</div>
#end proc
#
#proc renderBanner(profile: Profile): string = #proc renderBanner(profile: Profile): string =
#if "#" in profile.banner: #if "#" in profile.banner:
<div style="${profile.banner}" class="profile-banner-color"></div> <div style="${profile.banner}" class="profile-banner-color"></div>
@ -90,13 +107,17 @@
</div> </div>
#end proc #end proc
# #
#proc renderProfile*(profile: Profile; timeline: Timeline; beginning: bool): string = #proc renderProfile*(profile: Profile; timeline: Timeline;
# photoRail: seq[GalleryPhoto]; beginning: bool): string =
<div class="profile-tabs"> <div class="profile-tabs">
<div class="profile-banner"> <div class="profile-banner">
${renderBanner(profile)} ${renderBanner(profile)}
</div> </div>
<div class="profile-tab"> <div class="profile-tab">
${renderProfileCard(profile)} ${renderProfileCard(profile)}
#if photoRail.len > 0:
${renderPhotoRail(profile.username, photoRail)}
#end if
</div> </div>
<div class="timeline-tab"> <div class="timeline-tab">
#let link = "/" & profile.username #let link = "/" & profile.username