diff --git a/src/api.nim b/src/api.nim index a213992..ace31fb 100644 --- a/src/api.nim +++ b/src/api.nim @@ -40,6 +40,12 @@ proc getProfile*(username: string): Future[Profile] {.async.} = url = userShow ? ps result = parseUserShow(await fetch(url, oldApi=true), username) +proc getProfileById*(userId: string): Future[Profile] {.async.} = + let + ps = genParams({"user_id": userId}) + url = userShow ? ps + result = parseUserShowId(await fetch(url, oldApi=true), userId) + proc getTimeline*(id: string; after=""; replies=false): Future[Timeline] {.async.} = let ps = genParams({"userId": id, "include_tweet_replies": $replies}, after) diff --git a/src/parser.nim b/src/parser.nim index 5d3c74d..6ad41ba 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -38,6 +38,18 @@ proc parseUserShow*(js: JsonNode; username: string): Profile = result = parseProfile(js) +proc parseUserShowId*(js: JsonNode; userId: string): Profile = + if js.isNull: + return Profile(id: userId) + + with error, js{"errors"}: + result = Profile(id: userId) + if error.getError == suspended: + result.suspended = true + return + + result = parseProfile(js) + proc parseGraphProfile*(js: JsonNode; username: string): Profile = if js.isNull: return with error, js{"errors"}: diff --git a/src/redis_cache.nim b/src/redis_cache.nim index 83656a4..fcc5395 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -78,6 +78,7 @@ proc cache*(data: Profile) {.async.} = pool.withAcquire(r): r.startPipelining() discard await r.setex(name.profileKey, baseCacheTime, compress(toFlatty(data))) + discard await r.setex("i:" & data.id , baseCacheTime, data.username) discard await r.hset(name.pidKey, name, data.id) discard await r.flushPipeline() @@ -110,6 +111,15 @@ proc getCachedProfile*(username: string; fetch=true): Future[Profile] {.async.} elif fetch: result = await getProfile(username) +proc getCachedProfileScreenName*(userId: string): Future[string] {.async.} = + let username = await get("i:" & userId) + if username != redisNil: + result = username + else: + let profile = await getProfileById(userId) + result = profile.username + await cache(profile) + proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} = if name.len == 0: return let rail = await get("pr:" & toLower(name)) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 65d2652..aec715f 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -105,8 +105,22 @@ template respTimeline*(timeline: typed) = resp Http404, showError("User \"" & @"name" & "\" not found", cfg) resp t +template respUserId*() = + cond @"user_id".len > 0 + let username = await getCachedProfileScreenName(@"user_id") + if username.len > 0: + redirect("/" & username) + else: + resp Http404, showError("User not found", cfg) + proc createTimelineRouter*(cfg: Config) = router timeline: + get "/i/user/@user_id": + respUserId() + + get "/intent/user": + respUserId() + get "/@name/?@tab?/?": cond '.' notin @"name" cond @"name" notin ["pic", "gif", "video"] diff --git a/src/routes/unsupported.nim b/src/routes/unsupported.nim index 2cbcbb1..4bafb96 100644 --- a/src/routes/unsupported.nim +++ b/src/routes/unsupported.nim @@ -11,10 +11,13 @@ proc createUnsupportedRouter*(cfg: Config) = resp renderMain(renderFeature(), request, cfg, themePrefs()) get "/about/feature": feature() - get "/intent/?@i?": feature() get "/login/?@i?": feature() get "/@name/lists/?": feature() - get "/i/@i?/?@j?": - cond @"i" notin ["status", "lists"] + get "/intent/?@i?": + cond @"i" notin ["user"] + feature() + + get "/i/@i?/?@j?": + cond @"i" notin ["status", "lists" , "user"] feature()