Skip to content

Commit f9cb0a1

Browse files
committed
Implement Recommendations
1 parent 25191f7 commit f9cb0a1

File tree

11 files changed

+120
-10
lines changed

11 files changed

+120
-10
lines changed

src/api.nim

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ proc getProfile*(username: string): Future[Profile] {.async.} =
3939
url = userShow ? ps
4040
result = parseUserShow(await fetch(url, oldApi=true), username)
4141

42+
proc getRecommendations*(id: string) : Future[Recommendations] {.async.} =
43+
let
44+
ps = genParams({"user_id": id }) # , "limit": "100" 1) limit here or 2) fetch all and limit in display?
45+
url = recommendations ? ps
46+
result = parseRecommnedations(await fetch(url, oldApi = true))
47+
4248
proc getTimeline*(id: string; after=""; replies=false): Future[Timeline] {.async.} =
4349
let
4450
ps = genParams({"userId": id, "include_tweet_replies": $replies}, after)

src/consts.nim

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const
1010
userShow* = api / "1.1/users/show.json"
1111
photoRail* = api / "1.1/statuses/media_timeline.json"
1212
search* = api / "2/search/adaptive.json"
13+
recommendations* = api / "1.1/users/recommendations.json"
1314

1415
timelineApi = api / "2/timeline"
1516
tweet* = timelineApi / "conversation"

src/parser.nim

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ proc parseUserShow*(js: JsonNode; username: string): Profile =
3737

3838
result = parseProfile(js)
3939

40+
proc parseRecommnedations*(js: JsonNode) : Recommendations =
41+
for u in js:
42+
result.add parseProfile(u{"user"})
43+
4044
proc parseGraphProfile*(js: JsonNode; username: string): Profile =
4145
if js.isNull: return
4246
with error, js{"errors"}:

src/redis_cache.nim

+12
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ proc cache*(data: List) {.async.} =
6262
proc cache*(data: PhotoRail; name: string) {.async.} =
6363
await setex("pr:" & name, baseCacheTime, compress(freeze(data)))
6464

65+
proc cache*(data: Recommendations; user_id: string) {.async.} =
66+
await setex("rc:" & user_id , listCacheTime , compress(freeze(data)))
67+
6568
proc cache*(data: Profile) {.async.} =
6669
if data.username.len == 0 or data.id.len == 0: return
6770
let name = toLower(data.username)
@@ -109,6 +112,15 @@ proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} =
109112
result = await getPhotoRail(name)
110113
await cache(result, name)
111114

115+
proc getCachedRecommendations*(user_id: string ): Future[Recommendations] {.async.} =
116+
if user_id.len == 0: return
117+
let recommendations = await get("rc:" & user_id )
118+
if recommendations != redisNil:
119+
uncompress(recommendations).thaw(result)
120+
else:
121+
result = await getRecommendations(user_id)
122+
await cache(result,user_id)
123+
112124
proc getCachedList*(username=""; name=""; id=""): Future[List] {.async.} =
113125
let list = if id.len > 0: redisNil
114126
else: await get(toLower("l:" & username & '/' & name))

src/routes/rss.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ proc timelineRss*(req: Request; cfg: Config; query: Query): Future[Rss] {.async.
1919

2020
if names.len == 1:
2121
(profile, timeline) =
22-
await fetchSingleTimeline(after, query, skipRail=true)
22+
await fetchSingleTimeline(after, query, skipRail=true, skipRecommendations=true)
2323
else:
2424
var q = query
2525
q.fromUser = names

src/routes/timeline.nim

+13-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ proc getQuery*(request: Request; tab, name: string): Query =
1818
of "search": initQuery(params(request), name=name)
1919
else: Query(fromUser: @[name])
2020

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

2525
var
@@ -51,6 +51,13 @@ proc fetchSingleTimeline*(after: string; query: Query; skipRail=false):
5151
else:
5252
rail = getCachedPhotoRail(name)
5353

54+
var recommendations: Future[Recommendations]
55+
if skipRecommendations:
56+
recommendations = newFuture[Recommendations]()
57+
recommendations.complete(@[])
58+
else:
59+
recommendations= getCachedRecommendations(profileId)
60+
5461
var timeline =
5562
case query.kind
5663
of posts: await getTimeline(profileId, after)
@@ -75,7 +82,7 @@ proc fetchSingleTimeline*(after: string; query: Query; skipRail=false):
7582
if fetched and not found:
7683
await cache(profile)
7784

78-
return (profile, timeline, await rail)
85+
return (profile, timeline, await rail ,await recommendations)
7986

8087
proc get*(req: Request; key: string): string =
8188
params(req).getOrDefault(key)
@@ -88,12 +95,12 @@ proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs;
8895
html = renderTweetSearch(timeline, prefs, getPath())
8996
return renderMain(html, request, cfg, prefs, "Multi", rss=rss)
9097

91-
var (p, t, r) = await fetchSingleTimeline(after, query)
98+
var (p, t, r ,rc) = await fetchSingleTimeline(after, query)
9299

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

96-
let pHtml = renderProfile(p, t, r, prefs, getPath())
103+
let pHtml = renderProfile(p, t, r, rc, prefs, getPath())
97104
result = renderMain(pHtml, request, cfg, prefs, pageTitle(p), pageDesc(p),
98105
rss=rss, images = @[p.getUserpic("_400x400")],
99106
banner=p.banner)
@@ -126,7 +133,7 @@ proc createTimelineRouter*(cfg: Config) =
126133
timeline.beginning = true
127134
resp $renderTweetSearch(timeline, prefs, getPath())
128135
else:
129-
var (_, timeline, _) = await fetchSingleTimeline(after, query, skipRail=true)
136+
var (_, timeline, _ , _ ) = await fetchSingleTimeline(after, query, skipRail=true , skipRecommendations = true)
130137
if timeline.content.len == 0: resp Http404
131138
timeline.beginning = true
132139
resp $renderTimelineTweets(timeline, prefs, getPath())

src/sass/profile/_base.scss

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
@import 'card';
55
@import 'photo-rail';
6+
@import 'recommendations';
67

78
.profile-tabs {
89
@include panel(auto, 900px);

src/sass/profile/recommendations.scss

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@import '_variables';
2+
3+
.recommendations {
4+
&-card {
5+
float: left;
6+
background: var(--bg_panel);
7+
border-radius: 0 0 4px 4px;
8+
width: 100%;
9+
margin: 5px 0;
10+
}
11+
12+
&-header {
13+
padding: 5px 12px 0;
14+
}
15+
16+
&-header-mobile {
17+
display: none;
18+
box-sizing: border-box;
19+
padding: 5px 12px 0;
20+
width: 100%;
21+
float: unset;
22+
color: var(--accent);
23+
justify-content: space-between;
24+
}
25+
}
26+
27+
@include create-toggle(recommendations-list, 640px);
28+
#recommendations-list-toggle:checked ~ .recommendations-list {
29+
padding-bottom: 12px;
30+
}
31+
32+
@media(max-width: 600px) {
33+
.recommendations-header {
34+
display: none;
35+
}
36+
37+
.recommendations-header-mobile {
38+
display: flex;
39+
}
40+
41+
.recommendations-list {
42+
max-height: 0;
43+
padding-bottom: 0;
44+
overflow: scroll;
45+
transition: max-height 0.4s;
46+
}
47+
}
48+
49+
@media(max-width: 600px) {
50+
#recommendations-list-toggle:checked ~ .recommendations-list {
51+
max-height: 160px;
52+
}
53+
}
54+
55+
@media(max-width: 450px) {
56+
#recommendations-list-toggle:checked ~ .recommendations-list {
57+
max-height: 160px;
58+
}
59+
}

src/types.nim

+2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ type
9797

9898
PhotoRail* = seq[GalleryPhoto]
9999

100+
Recommendations* = seq[Profile]
101+
100102
Poll* = object
101103
options*: seq[string]
102104
values*: seq[int]

src/views/profile.nim

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import strutils, strformat
22
import karax/[karaxdsl, vdom, vstyles]
33

4-
import renderutils, search
4+
import renderutils, search, timeline
55
import ".."/[types, utils, formatters]
66

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

83+
proc renderRecommendations(recommendations: Recommendations, prefs: Prefs): VNode =
84+
buildHtml(tdiv(class="recommendations-card")):
85+
tdiv(class="recommendations-header"):
86+
span: text "You might like"
87+
88+
input(id="recommendations-list-toggle", `type`="checkbox")
89+
label(`for`="recommendations-list-toggle", class="recommendations-header-mobile"):
90+
span: text "You might like"
91+
icon "down"
92+
93+
tdiv(class="recommendations-list"):
94+
for i , recommendation in recommendations:
95+
renderUser(recommendation, prefs)
96+
8397
proc renderBanner(profile: Profile): VNode =
8498
buildHtml():
8599
if "#" in profile.banner:
@@ -95,7 +109,7 @@ proc renderProtected(username: string): VNode =
95109
p: text &"Only confirmed followers have access to @{username}'s tweets."
96110

97111
proc renderProfile*(profile: Profile; timeline: var Timeline;
98-
photoRail: PhotoRail; prefs: Prefs; path: string): VNode =
112+
photoRail: PhotoRail; recommendations: Recommendations; prefs: Prefs; path: string): VNode =
99113
timeline.query.fromUser = @[profile.username]
100114
buildHtml(tdiv(class="profile-tabs")):
101115
if not prefs.hideBanner:
@@ -107,6 +121,10 @@ proc renderProfile*(profile: Profile; timeline: var Timeline;
107121
renderProfileCard(profile, prefs)
108122
if photoRail.len > 0:
109123
renderPhotoRail(profile, photoRail)
124+
if recommendations.len > 0:
125+
renderRecommendations(recommendations, prefs)
126+
127+
110128

111129
if profile.protected:
112130
renderProtected(profile.username)

src/views/timeline.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet
5656
elif t.replyId == result[0].id:
5757
result.add t
5858

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

0 commit comments

Comments
 (0)