Skip to content

Commit

Permalink
Implement Recommendations
Browse files Browse the repository at this point in the history
  • Loading branch information
jackyzy823 committed Dec 30, 2021
1 parent ebffb6d commit 3a279e2
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 10 deletions.
6 changes: 6 additions & 0 deletions src/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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 @@ -434,6 +434,10 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline =
elif "cursor-bottom" in entry:
result.bottom = e.getCursor

proc parseRecommnedations*(js: JsonNode): Recommendations =
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 @@ -72,6 +72,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 @@ -96,6 +99,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 get*(req: Request; key: string): string =
params(req).getOrDefault(key)
Expand All @@ -89,12 +96,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 @@ -127,7 +134,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

0 comments on commit 3a279e2

Please sign in to comment.