Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Recommendations #444

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ proc getTweet*(id: string; after=""): Future[Conversation] {.async.} =
if after.len > 0:
result.replies = await getReplies(id, after)

proc getRecommendations*(id: string): Future[Recommendations] {.async.} =
let
ps = genParams({"user_id": id})
url = recommendations ? ps
result = parseRecommnedations(await fetch(url, oldApi=true))

proc resolve*(url: string; prefs: Prefs): Future[string] {.async.} =
let client = newAsyncHttpClient(maxRedirects=0)
try:
Expand Down
1 change: 1 addition & 0 deletions src/consts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const
userShow* = api / "1.1/users/show.json"
photoRail* = api / "1.1/statuses/media_timeline.json"
search* = api / "2/search/adaptive.json"
recommendations* = api / "1.1/users/recommendations.json"

timelineApi = api / "2/timeline"
tweet* = timelineApi / "conversation"
Expand Down
4 changes: 4 additions & 0 deletions src/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline =
elif "cursor-bottom" in entry:
result.bottom = e.getCursor

proc parseRecommnedations*(js: JsonNode): Recommendations =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

for u in js:
result.add parseProfile(u{"user"})

proc parsePhotoRail*(js: JsonNode): PhotoRail =
for tweet in js:
let
Expand Down
12 changes: 12 additions & 0 deletions src/redis_cache.nim
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ proc cache*(data: List) {.async.} =
proc cache*(data: PhotoRail; name: string) {.async.} =
await setex("pr:" & name, baseCacheTime, compress(toFlatty(data)))

proc cache*(data: Recommendations; userId: string) {.async.} =
await setex("rc:" & userId, listCacheTime, compress(toFlatty(data)))

proc cache*(data: Profile) {.async.} =
if data.username.len == 0 or data.id.len == 0: return
let name = toLower(data.username)
Expand All @@ -99,6 +102,15 @@ proc cacheRss*(query: string; rss: Rss) {.async.} =
discard await r.expire(key, rssCacheTime)
discard await r.flushPipeline()

proc getCachedRecommendations*(userId: string): Future[Recommendations] {.async.} =
if userId.len == 0: return
let recommendations = await get("rc:" & userId)
if recommendations != redisNil:
result = fromFlatty(uncompress(recommendations), Recommendations)
else:
result = await getRecommendations(userId)
await cache(result, userId)

proc getProfileId*(username: string): Future[string] {.async.} =
let name = toLower(username)
pool.withAcquire(r):
Expand Down
2 changes: 1 addition & 1 deletion src/routes/rss.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async.

if names.len == 1:
(profile, timeline) =
await fetchSingleTimeline(after, query, skipRail=true)
await fetchSingleTimeline(after, query, skipRail=true, skipRecommendations=true)
else:
var q = query
q.fromUser = names
Expand Down
19 changes: 13 additions & 6 deletions src/routes/timeline.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ proc getQuery*(request: Request; tab, name: string): Query =
of "search": initQuery(params(request), name=name)
else: Query(fromUser: @[name])

proc fetchSingleTimeline*(after: string; query: Query; skipRail=false):
Future[(Profile, Timeline, PhotoRail)] {.async.} =
proc fetchSingleTimeline*(after: string; query: Query; skipRail=false, skipRecommendations=false):
Future[(Profile, Timeline, PhotoRail, Recommendations)] {.async.} =
let name = query.fromUser[0]

var
Expand Down Expand Up @@ -52,6 +52,13 @@ proc fetchSingleTimeline*(after: string; query: Query; skipRail=false):
else:
rail = getCachedPhotoRail(name)

var recommendations: Future[Recommendations]
if skipRecommendations:
recommendations = newFuture[Recommendations]()
recommendations.complete(@[])
else:
recommendations = getCachedRecommendations(profileId)

var timeline =
case query.kind
of posts: await getTimeline(profileId, after)
Expand All @@ -76,7 +83,7 @@ proc fetchSingleTimeline*(after: string; query: Query; skipRail=false):
if fetched and not found:
await cache(profile)

return (profile, timeline, await rail)
return (profile, timeline, await rail, await recommendations)

proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs;
rss, after: string): Future[string] {.async.} =
Expand All @@ -86,12 +93,12 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs;
html = renderTweetSearch(timeline, prefs, getPath())
return renderMain(html, request, cfg, prefs, "Multi", rss=rss)

var (p, t, r) = await fetchSingleTimeline(after, query)
var (p, t, r, rc) = await fetchSingleTimeline(after, query)

if p.suspended: return showError(getSuspended(p.username), cfg)
if p.id.len == 0: return

let pHtml = renderProfile(p, t, r, prefs, getPath())
let pHtml = renderProfile(p, t, r, rc, prefs, getPath())
result = renderMain(pHtml, request, cfg, prefs, pageTitle(p), pageDesc(p),
rss=rss, images = @[p.getUserpic("_400x400")],
banner=p.banner)
Expand Down Expand Up @@ -139,7 +146,7 @@ proc createTimelineRouter*(cfg: Config) =
timeline.beginning = true
resp $renderTweetSearch(timeline, prefs, getPath())
else:
var (_, timeline, _) = await fetchSingleTimeline(after, query, skipRail=true)
var (_, timeline, _, _) = await fetchSingleTimeline(after, query, skipRail=true, skipRecommendations=true)
if timeline.content.len == 0: resp Http404
timeline.beginning = true
resp $renderTimelineTweets(timeline, prefs, getPath())
Expand Down
1 change: 1 addition & 0 deletions src/sass/profile/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@import 'card';
@import 'photo-rail';
@import 'recommendations';

.profile-tabs {
@include panel(auto, 900px);
Expand Down
59 changes: 59 additions & 0 deletions src/sass/profile/recommendations.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@import '_variables';

.recommendations {
&-card {
float: left;
background: var(--bg_panel);
border-radius: 0 0 4px 4px;
width: 100%;
margin: 5px 0;
}

&-header {
padding: 5px 12px 0;
}

&-header-mobile {
display: none;
box-sizing: border-box;
padding: 5px 12px 0;
width: 100%;
float: unset;
color: var(--accent);
justify-content: space-between;
}
}

@include create-toggle(recommendations-list, 640px);
#recommendations-list-toggle:checked ~ .recommendations-list {
padding-bottom: 12px;
}

@media(max-width: 600px) {
.recommendations-header {
display: none;
}

.recommendations-header-mobile {
display: flex;
}

.recommendations-list {
max-height: 0;
padding-bottom: 0;
overflow: scroll;
transition: max-height 0.4s;
}
}

@media(max-width: 600px) {
#recommendations-list-toggle:checked ~ .recommendations-list {
max-height: 160px;
}
}

@media(max-width: 450px) {
#recommendations-list-toggle:checked ~ .recommendations-list {
max-height: 160px;
}
}
2 changes: 2 additions & 0 deletions src/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ type

PhotoRail* = seq[GalleryPhoto]

Recommendations* = seq[Profile]

Poll* = object
options*: seq[string]
values*: seq[int]
Expand Down
21 changes: 19 additions & 2 deletions src/views/profile.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import strutils, strformat
import karax/[karaxdsl, vdom, vstyles]

import renderutils, search
import renderutils, search, timeline
import ".."/[types, utils, formatters]

proc renderStat(num, class: string; text=""): VNode =
Expand Down Expand Up @@ -81,6 +81,20 @@ proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
style={backgroundColor: col}):
genImg(photo.url & (if "format" in photo.url: "" else: ":thumb"))

proc renderRecommendations(recommendations: Recommendations; prefs: Prefs): VNode =
buildHtml(tdiv(class="recommendations-card")):
tdiv(class="recommendations-header"):
span: text "You might like"

input(id="recommendations-list-toggle", `type`="checkbox")
label(`for`="recommendations-list-toggle", class="recommendations-header-mobile"):
span: text "You might like"
icon "down"

tdiv(class="recommendations-list"):
for i, recommendation in recommendations:
renderUser(recommendation, prefs)

proc renderBanner(profile: Profile): VNode =
buildHtml():
if "#" in profile.banner:
Expand All @@ -96,7 +110,7 @@ proc renderProtected(username: string): VNode =
p: text &"Only confirmed followers have access to @{username}'s tweets."

proc renderProfile*(profile: Profile; timeline: var Timeline;
photoRail: PhotoRail; prefs: Prefs; path: string): VNode =
photoRail: PhotoRail; recommendations: Recommendations; prefs: Prefs; path: string): VNode =
timeline.query.fromUser = @[profile.username]
buildHtml(tdiv(class="profile-tabs")):
if not prefs.hideBanner:
Expand All @@ -108,6 +122,9 @@ proc renderProfile*(profile: Profile; timeline: var Timeline;
renderProfileCard(profile, prefs)
if photoRail.len > 0:
renderPhotoRail(profile, photoRail)
if recommendations.len > 0:
renderRecommendations(recommendations, prefs)


if profile.protected:
renderProtected(profile.username)
Expand Down
2 changes: 1 addition & 1 deletion src/views/timeline.nim
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet
elif t.replyId == result[0].id:
result.add t

proc renderUser(user: Profile; prefs: Prefs): VNode =
proc renderUser*(user: Profile; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline-item")):
a(class="tweet-link", href=("/" & user.username))
tdiv(class="tweet-body profile-result"):
Expand Down