re-enable redis and rss im too lazy to make another sort of caching system

This commit is contained in:
Cynthia Foxwell 2025-04-09 00:15:25 -06:00
parent 91d3f4138c
commit 68e90344d6
No known key found for this signature in database
20 changed files with 113 additions and 112 deletions

View File

@ -28,7 +28,6 @@ maintained by the community.
* Image zooming/carousel (requires JavaScript) * Image zooming/carousel (requires JavaScript)
* Up to date Twitter features, e.g. Community Notes * Up to date Twitter features, e.g. Community Notes
* Embeds for chat services on-par with services like [FxTwitter](https://github.com/FixTweet/FxTwitter) and [vxTwitter](https://github.com/dylanpdx/BetterTwitFix) * Embeds for chat services on-par with services like [FxTwitter](https://github.com/FixTweet/FxTwitter) and [vxTwitter](https://github.com/dylanpdx/BetterTwitFix)
* No dependency on Redis, as it has caused ratelimiting issues, but also forcably disables RSS
## Why use Nitter? ## Why use Nitter?

View File

@ -95,7 +95,7 @@ proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} =
js = await fetch(graphTweetResult ? params, Api.tweetResult) js = await fetch(graphTweetResult ? params, Api.tweetResult)
result = parseGraphTweetResult(js) result = parseGraphTweetResult(js)
proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} = proc getGraphTweet*(id: string; after=""): Future[Conversation] {.async.} =
if id.len == 0: return if id.len == 0: return
let let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: "" cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""

View File

@ -64,7 +64,7 @@ proc toUser*(raw: RawUser): User =
) )
if raw.pinnedTweetIdsStr.len > 0: if raw.pinnedTweetIdsStr.len > 0:
result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0]) result.pinnedTweet = raw.pinnedTweetIdsStr[0]
result.expandUserEntities(raw) result.expandUserEntities(raw)

View File

@ -149,7 +149,7 @@ proc getShortTime*(tweet: Tweet): string =
result = "now" result = "now"
proc getLink*(tweet: Tweet; focus=true): string = proc getLink*(tweet: Tweet; focus=true): string =
if tweet.id == 0: return if tweet.id.len == 0: return
var username = tweet.user.username var username = tweet.user.username
if username.len == 0: if username.len == 0:
username = "i" username = "i"

View File

@ -9,7 +9,7 @@ import jester
import types, config, prefs, formatters, redis_cache, http_pool import types, config, prefs, formatters, redis_cache, http_pool
import views/[general, about] import views/[general, about]
import routes/[ import routes/[
preferences, timeline, status, media, search, list, #rss, debug, preferences, timeline, status, media, search, list, rss, #debug,
unsupported, embed, resolver, router_utils, home, follow, twitter_api, unsupported, embed, resolver, router_utils, home, follow, twitter_api,
activityspoof] activityspoof]
@ -36,9 +36,9 @@ setMaxHttpConns(cfg.httpMaxConns)
setHttpProxy(cfg.proxy, cfg.proxyAuth) setHttpProxy(cfg.proxy, cfg.proxyAuth)
initAboutPage(cfg.staticDir) initAboutPage(cfg.staticDir)
#waitFor initRedisPool(cfg) waitFor initRedisPool(cfg)
#stdout.write &"Connected to Redis at {cfg.redisHost}:{cfg.redisPort}\n" stdout.write &"Connected to Redis at {cfg.redisHost}:{cfg.redisPort}\n"
#stdout.flushFile stdout.flushFile
createUnsupportedRouter(cfg) createUnsupportedRouter(cfg)
createResolverRouter(cfg) createResolverRouter(cfg)
@ -49,7 +49,7 @@ createStatusRouter(cfg)
createSearchRouter(cfg) createSearchRouter(cfg)
createMediaRouter(cfg) createMediaRouter(cfg)
createEmbedRouter(cfg) createEmbedRouter(cfg)
#createRssRouter(cfg) createRssRouter(cfg)
#createDebugRouter(cfg) #createDebugRouter(cfg)
createTwitterApiRouter(cfg) createTwitterApiRouter(cfg)
createActivityPubRouter(cfg) createActivityPubRouter(cfg)
@ -95,7 +95,7 @@ routes:
extend home, "" extend home, ""
extend follow, "" extend follow, ""
#extend rss, "" extend rss, ""
extend status, "" extend status, ""
extend search, "" extend search, ""
extend timeline, "" extend timeline, ""

View File

@ -205,9 +205,9 @@ proc parseCard(js: JsonNode; urls: JsonNode): Card =
proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet = proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
if js.isNull: return if js.isNull: return
result = Tweet( result = Tweet(
id: js{"id_str"}.getId, id: js{"id_str"}.getStr,
threadId: js{"conversation_id_str"}.getId, threadId: js{"conversation_id_str"}.getStr,
replyId: js{"in_reply_to_status_id_str"}.getId, replyId: js{"in_reply_to_status_id_str"}.getStr,
replyHandle: js{"in_reply_to_screen_name"}.getStr, replyHandle: js{"in_reply_to_screen_name"}.getStr,
text: js{"full_text"}.getStr, text: js{"full_text"}.getStr,
time: js{"created_at"}.getTime, time: js{"created_at"}.getTime,
@ -223,17 +223,17 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
) )
# fix for pinned threads # fix for pinned threads
if result.hasThread and result.threadId == 0: if result.hasThread and result.threadId.len == 0:
result.threadId = js{"self_thread", "id_str"}.getId result.threadId = js{"self_thread", "id_str"}.getStr
if "retweeted_status" in js: if "retweeted_status" in js:
result.retweet = some Tweet() result.retweet = some Tweet()
elif js{"is_quote_status"}.getBool: elif js{"is_quote_status"}.getBool:
result.quote = some Tweet(id: js{"quoted_status_id_str"}.getId) result.quote = some Tweet(id: js{"quoted_status_id_str"}.getStr)
# legacy # legacy
with rt, js{"retweeted_status_id_str"}: with rt, js{"retweeted_status_id_str"}:
result.retweet = some Tweet(id: rt.getId) result.retweet = some Tweet(id: rt.getStr)
return return
# graphql # graphql
@ -324,11 +324,10 @@ proc parseTweetSearch*(js: JsonNode; after=""): Timeline =
result.content.add @[parsed] result.content.add @[parsed]
if result.content.len > 0: if result.content.len > 0:
result.bottom = $(result.content[^1][0].id - 1) result.bottom = $(result.content[^1][0].id.parseBiggestInt() - 1)
proc finalizeTweet(global: GlobalObjects; id: string): Tweet = proc finalizeTweet(global: GlobalObjects; id: string): Tweet =
let intId = if id.len > 0: parseBiggestInt(id) else: 0 result = global.tweets.getOrDefault(id, Tweet(id: id))
result = global.tweets.getOrDefault(id, Tweet(id: intId))
if result.quote.isSome: if result.quote.isSome:
let quote = get(result.quote).id let quote = get(result.quote).id
@ -463,7 +462,7 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
jsCard["binding_values"] = values jsCard["binding_values"] = values
result = parseTweet(js{"legacy"}, jsCard) result = parseTweet(js{"legacy"}, jsCard)
result.id = js{"rest_id"}.getId result.id = js{"rest_id"}.getStr
result.user = parseGraphUser(js{"core"}) result.user = parseGraphUser(js{"core"})
with noteTweet, js{"note_tweet", "note_tweet_results", "result"}: with noteTweet, js{"note_tweet", "note_tweet_results", "result"}:
@ -474,7 +473,7 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
with communityNote, js{"birdwatch_pivot"}: with communityNote, js{"birdwatch_pivot"}:
let note = BirdwatchNote( let note = BirdwatchNote(
id: communityNote{"note", "rest_id"}.getId, id: communityNote{"note", "rest_id"}.getStr,
title: communityNote{"title"}.getStr, title: communityNote{"title"}.getStr,
) )
note.expandBirdwatchEntities(communityNote{"subtitle"}) note.expandBirdwatchEntities(communityNote{"subtitle"})
@ -519,16 +518,16 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation =
let tweet = parseGraphTweet(tweetResult, true) let tweet = parseGraphTweet(tweetResult, true)
if not tweet.available: if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId()) tweet.id = entryId.getId()
if $tweet.id == tweetId: if tweet.id == tweetId:
result.tweet = tweet result.tweet = tweet
else: else:
result.before.content.add tweet result.before.content.add tweet
elif entryId.startsWith("tombstone"): elif entryId.startsWith("tombstone"):
let id = entryId.getId() let id = entryId.getId()
let tweet = Tweet( let tweet = Tweet(
id: parseBiggestInt(id), id: id,
available: false, available: false,
text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone text: e{"content", "itemContent", "tombstoneInfo", "richText"}.getTombstone
) )
@ -565,7 +564,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile =
with tweetResult, e{"content", "content", "tweetResult", "result"}: with tweetResult, e{"content", "content", "tweetResult", "result"}:
let tweet = parseGraphTweet(tweetResult, false) let tweet = parseGraphTweet(tweetResult, false)
if not tweet.available: if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId()) tweet.id = entryId.getId()
result.tweets.content.add tweet result.tweets.content.add tweet
elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): elif "-conversation-" in entryId or entryId.startsWith("homeConversation"):
let (thread, self) = parseGraphThread(e) let (thread, self) = parseGraphThread(e)
@ -580,7 +579,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile =
with tweetResult, e{"content", "itemContent", "tweet_results", "result"}: with tweetResult, e{"content", "itemContent", "tweet_results", "result"}:
let tweet = parseGraphTweet(tweetResult, false) let tweet = parseGraphTweet(tweetResult, false)
if not tweet.available: if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId()) tweet.id = entryId.getId()
result.tweets.content.add tweet result.tweets.content.add tweet
elif "-conversation-" in entryId or entryId.startsWith("homeConversation"): elif "-conversation-" in entryId or entryId.startsWith("homeConversation"):
let (thread, self) = parseGraphThread(e) let (thread, self) = parseGraphThread(e)
@ -594,7 +593,7 @@ proc parseGraphTimeline*(js: JsonNode; root: string; after=""): Profile =
if not tweet.available and tweet.tombstone.len == 0: if not tweet.available and tweet.tombstone.len == 0:
let entryId = i{"entry", "entryId"}.getEntryId let entryId = i{"entry", "entryId"}.getEntryId
if entryId.len > 0: if entryId.len > 0:
tweet.id = parseBiggestInt(entryId) tweet.id = entryId
result.pinned = some tweet result.pinned = some tweet
proc parseGraphUsersTimeline(timeline: JsonNode; after=""): UsersTimeline = proc parseGraphUsersTimeline(timeline: JsonNode; after=""): UsersTimeline =
@ -644,7 +643,7 @@ proc parseGraphSearch*[T: User | Tweets](js: JsonNode; after=""): Result[T] =
with tweetRes, e{"content", "itemContent", "tweet_results", "result"}: with tweetRes, e{"content", "itemContent", "tweet_results", "result"}:
let tweet = parseGraphTweet(tweetRes) let tweet = parseGraphTweet(tweetRes)
if not tweet.available: if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId()) tweet.id = entryId.getId()
result.content.add tweet result.content.add tweet
elif T is User: elif T is User:
if entryId.startsWith("user"): if entryId.startsWith("user"):

View File

@ -286,7 +286,7 @@ proc expandTextEntities(tweet: Tweet; entities: JsonNode; text: string; textSlic
url: "/" & name, display: mention["name"].getStr) url: "/" & name, display: mention["name"].getStr)
if idx > -1 and name != replyTo: if idx > -1 and name != replyTo:
tweet.reply.delete idx tweet.reply.delete idx
elif idx == -1 and tweet.replyId != 0: elif idx == -1 and tweet.replyId.len != 0:
tweet.reply.add name tweet.reply.add name
replacements.deduplicate replacements.deduplicate
@ -303,7 +303,7 @@ proc expandTweetEntities*(tweet: Tweet; js: JsonNode) =
hasJobCard = tweet.card.isSome and get(tweet.card).kind == jobDetails hasJobCard = tweet.card.isSome and get(tweet.card).kind == jobDetails
var replyTo = "" var replyTo = ""
if tweet.replyId != 0: if tweet.replyId.len != 0:
with reply, js{"in_reply_to_screen_name"}: with reply, js{"in_reply_to_screen_name"}:
replyTo = reply.getStr replyTo = reply.getStr
tweet.reply.add replyTo tweet.reply.add replyTo

View File

@ -66,7 +66,8 @@ proc initRedisPool*(cfg: Config) {.async.} =
template uidKey(name: string): string = "pid:" & $(hash(name) div 1_000_000) template uidKey(name: string): string = "pid:" & $(hash(name) div 1_000_000)
template userKey(name: string): string = "p:" & name template userKey(name: string): string = "p:" & name
template listKey(l: List): string = "l:" & l.id template listKey(l: List): string = "l:" & l.id
template tweetKey(id: int64): string = "t:" & $id template tweetKey(id: string): string = "t:" & id
template convKey(id: string): string = "c:" & id
proc get(query: string): Future[string] {.async.} = proc get(query: string): Future[string] {.async.} =
pool.withAcquire(r): pool.withAcquire(r):
@ -96,10 +97,15 @@ proc cache*(data: User) {.async.} =
dawait r.setEx(name.userKey, baseCacheTime, compress(toFlatty(data))) dawait r.setEx(name.userKey, baseCacheTime, compress(toFlatty(data)))
proc cache*(data: Tweet) {.async.} = proc cache*(data: Tweet) {.async.} =
if data.isNil or data.id == 0: return if data.isNil or data.id.len == 0: return
pool.withAcquire(r): pool.withAcquire(r):
dawait r.setEx(data.id.tweetKey, baseCacheTime, compress(toFlatty(data))) dawait r.setEx(data.id.tweetKey, baseCacheTime, compress(toFlatty(data)))
proc cache*(data: Conversation) {.async.} =
if data.isNil or data.tweet.isNil or data.tweet.id.len == 0: return
pool.withAcquire(r):
dawait r.setEx(data.tweet.id.convKey, baseCacheTime, compress(toFlatty(data)))
proc cacheRss*(query: string; rss: Rss) {.async.} = proc cacheRss*(query: string; rss: Rss) {.async.} =
let key = "rss:" & query let key = "rss:" & query
pool.withAcquire(r): pool.withAcquire(r):
@ -114,7 +120,13 @@ template deserialize(data, T) =
except: except:
echo "Decompression failed($#): '$#'" % [astToStr(T), data] echo "Decompression failed($#): '$#'" % [astToStr(T), data]
proc getUserId*(username: string): Future[string] {.async.} = proc deserializeConversation(data: string): Conversation =
try:
result = fromFlatty(uncompress(data), Conversation)
except:
echo "Decompression failed(Conversation): '$#'" % [data]
proc getCachedUserId*(username: string): Future[string] {.async.} =
let name = toLower(username) let name = toLower(username)
pool.withAcquire(r): pool.withAcquire(r):
result = await r.hGet(name.uidKey, name) result = await r.hGet(name.uidKey, name)
@ -148,15 +160,19 @@ proc getCachedUsername*(userId: string): Future[string] {.async.} =
if result.len > 0 and user.id.len > 0: if result.len > 0 and user.id.len > 0:
await all(cacheUserId(result, user.id), cache(user)) await all(cacheUserId(result, user.id), cache(user))
# proc getCachedTweet*(id: int64): Future[Tweet] {.async.} = proc getCachedTweet*(id: string; after=""): Future[Conversation] {.async.} =
# if id == 0: return if id.len == 0: return
# let tweet = await get(id.tweetKey) let tweet = await get(id.tweetKey)
# if tweet != redisNil:
# tweet.deserialize(Tweet) if tweet != redisNil:
# else: result = deserializeConversation(tweet)
# result = await getGraphTweetResult($id) else:
# if not result.isNil: result = await getGraphTweet(id)
# await cache(result) if not result.isNil:
await cache(result)
if not result.isNil and after.len > 0:
result.replies = await getReplies(id, after)
proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} = proc getCachedPhotoRail*(name: string): Future[PhotoRail] {.async.} =
if name.len == 0: return if name.len == 0: return

View File

@ -4,7 +4,7 @@ import json, asyncdispatch, strutils, sequtils, uri, options, sugar, strformat,
import jester import jester
import router_utils import router_utils
import ".."/[types, formatters, api] import ".."/[types, formatters, api, redis_cache]
import ../views/[mastoapi] import ../views/[mastoapi]
export json, uri, sequtils, options, sugar, times export json, uri, sequtils, options, sugar, times
@ -25,11 +25,11 @@ proc createActivityPubRouter*(cfg: Config) =
let prefs = cookiePrefs() let prefs = cookiePrefs()
let conv = await getTweet(id) let conv = await getCachedTweet(id)
if conv == nil: if conv == nil:
echo "nil conv" echo "nil conv"
if conv == nil or conv.tweet == nil or conv.tweet.id == 0: if conv == nil or conv.tweet == nil or conv.tweet.id.len == 0:
var error = "Record not found" var error = "Record not found"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0: if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone error = conv.tweet.tombstone
@ -112,8 +112,8 @@ proc createActivityPubRouter*(cfg: Config) =
postJson["created_at"] = %($tweet.time) postJson["created_at"] = %($tweet.time)
postJson["edited_at"] = newJNull() postJson["edited_at"] = newJNull()
postJson["reblog"] = newJNull() postJson["reblog"] = newJNull()
if tweet.replyId != 0: if tweet.replyId.len != 0:
let replyUser = await getGraphUser(tweet.replyHandle) let replyUser = await getCachedUser(tweet.replyHandle)
postJson["in_reply_to_id"] = %(&"{tweet.replyId}") postJson["in_reply_to_id"] = %(&"{tweet.replyId}")
postJson["in_reply_to_account_id"] = %replyUser.id postJson["in_reply_to_account_id"] = %replyUser.id
else: else:
@ -170,11 +170,11 @@ proc createActivityPubRouter*(cfg: Config) =
let prefs = cookiePrefs() let prefs = cookiePrefs()
let conv = await getTweet(id) let conv = await getCachedTweet(id)
if conv == nil: if conv == nil:
echo "nil conv" echo "nil conv"
if conv == nil or conv.tweet == nil or conv.tweet.id == 0: if conv == nil or conv.tweet == nil or conv.tweet.id.len == 0:
var error = "Record not found" var error = "Record not found"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0: if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone error = conv.tweet.tombstone
@ -192,7 +192,7 @@ proc createActivityPubRouter*(cfg: Config) =
get "/users/@name": get "/users/@name":
if request.headers.hasKey("Accept") and request.headers["Accept"] == "application/activity+json": if request.headers.hasKey("Accept") and request.headers["Accept"] == "application/activity+json":
let user = await getGraphUser(@"name") let user = await getCachedUser(@"name")
if user.suspended or user.id.len == 0: if user.suspended or user.id.len == 0:
resp Http404, {"Content-Type": "application/json"}, """{"error":"User not found"}""" resp Http404, {"Content-Type": "application/json"}, """{"error":"User not found"}"""

View File

@ -1,6 +1,6 @@
import jester import jester
import asyncdispatch, strutils, options, router_utils, timeline import asyncdispatch, strutils, options, router_utils, timeline
import ".."/[prefs, types, utils] import ".."/[prefs, types, utils, redis_cache]
import ../views/[general, home, search] import ../views/[general, home, search]
export home export home
@ -43,7 +43,7 @@ proc createHomeRouter*(cfg: Config) =
query.kind = userList query.kind = userList
for name in names: for name in names:
let prof = await getGraphUser(name) let prof = await getCachedUser(name)
profs &= @[prof] profs &= @[prof]
resp renderMain(renderFollowing(query, profs, prefs), request, cfg, prefs) resp renderMain(renderFollowing(query, profs, prefs), request, cfg, prefs)

View File

@ -4,7 +4,7 @@ import strutils, strformat, uri
import jester import jester
import router_utils import router_utils
import ".."/[types, api] import ".."/[types, api, redis_cache]
import ../views/[general, timeline, list] import ../views/[general, timeline, list]
template respList*(list, timeline, title, vnode: typed) = template respList*(list, timeline, title, vnode: typed) =
@ -36,7 +36,7 @@ proc createListRouter*(cfg: Config) =
cond @"slug" != "memberships" cond @"slug" != "memberships"
let let
slug = decodeUrl(@"slug") slug = decodeUrl(@"slug")
list = await getList(@"name", slug) list = await getCachedList(@"name", slug)
if list.id.len == 0: if list.id.len == 0:
resp Http404, showError(&"""List "{@"slug"}" not found""", cfg) resp Http404, showError(&"""List "{@"slug"}" not found""", cfg)
redirect(&"/i/lists/{list.id}") redirect(&"/i/lists/{list.id}")
@ -45,7 +45,7 @@ proc createListRouter*(cfg: Config) =
cond '.' notin @"id" cond '.' notin @"id"
let let
prefs = cookiePrefs() prefs = cookiePrefs()
list = await getList(id=(@"id")) list = await getCachedList(id=(@"id"))
timeline = await getGraphListTweets(list.id, getCursor()) timeline = await getGraphListTweets(list.id, getCursor())
vnode = renderTimelineTweets(timeline, prefs, request.path) vnode = renderTimelineTweets(timeline, prefs, request.path)
respList(list, timeline, list.title, vnode) respList(list, timeline, list.title, vnode)
@ -54,6 +54,6 @@ proc createListRouter*(cfg: Config) =
cond '.' notin @"id" cond '.' notin @"id"
let let
prefs = cookiePrefs() prefs = cookiePrefs()
list = await getList(id=(@"id")) list = await getCachedList(id=(@"id"))
members = await getGraphListMembers(list, getCursor()) members = await getGraphListMembers(list, getCursor())
respList(list, members, list.title, renderTimelineUsers(members, prefs, request.path)) respList(list, members, list.title, renderTimelineUsers(members, prefs, request.path))

View File

@ -4,7 +4,7 @@ import json, asyncdispatch, strutils, sequtils, uri, options, sugar, strformat,
import jester, karax/vdom import jester, karax/vdom
import router_utils import router_utils
import ".."/[types, formatters, api] import ".."/[types, formatters, api, redis_cache]
import ../views/[general, status, search, mastoapi] import ../views/[general, status, search, mastoapi]
export json, uri, sequtils, options, sugar, times export json, uri, sequtils, options, sugar, times
@ -51,11 +51,11 @@ proc createStatusRouter*(cfg: Config) =
let prefs = cookiePrefs() let prefs = cookiePrefs()
let conv = await getTweet(id) let conv = await getCachedTweet(id)
if conv == nil: if conv == nil:
echo "nil conv" echo "nil conv"
if conv == nil or conv.tweet == nil or conv.tweet.id == 0: if conv == nil or conv.tweet == nil or conv.tweet.id.len == 0:
var error = "Record not found" var error = "Record not found"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0: if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone error = conv.tweet.tombstone
@ -81,11 +81,11 @@ proc createStatusRouter*(cfg: Config) =
resp Http404, "" resp Http404, ""
resp $renderReplies(replies, prefs, getPath()) resp $renderReplies(replies, prefs, getPath())
let conv = await getTweet(id, getCursor()) let conv = await getCachedTweet(id, getCursor())
if conv == nil: if conv == nil:
echo "nil conv" echo "nil conv"
if conv == nil or conv.tweet == nil or conv.tweet.id == 0: if conv == nil or conv.tweet == nil or conv.tweet.id.len == 0:
var error = "Tweet not found" var error = "Tweet not found"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0: if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone error = conv.tweet.tombstone
@ -109,15 +109,15 @@ proc createStatusRouter*(cfg: Config) =
let let
quote = get(tweet.quote) quote = get(tweet.quote)
quoteUser = quote.user quoteUser = quote.user
if tweet.replyId != 0: if tweet.replyId.len != 0:
let replyUser = await getGraphUser(tweet.replyHandle) let replyUser = await getCachedUser(tweet.replyHandle)
context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})\n↘ {quoteUser.fullname} (@{quoteUser.username})" context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})\n↘ {quoteUser.fullname} (@{quoteUser.username})"
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}" contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
else: else:
context = &"↘ {quoteUser.fullname} (@{quoteUser.username})" context = &"↘ {quoteUser.fullname} (@{quoteUser.username})"
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{quote.id}" contextUrl = &"{getUrlPrefix(cfg)}/i/status/{quote.id}"
elif tweet.replyId != 0: elif tweet.replyId.len != 0:
let replyUser = await getGraphUser(tweet.replyHandle) let replyUser = await getCachedUser(tweet.replyHandle)
context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})" context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})"
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}" contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"

View File

@ -3,13 +3,13 @@ import asyncdispatch, strutils, sequtils, uri, options, times, json
import jester, karax/vdom import jester, karax/vdom
import router_utils import router_utils
import ".."/[types, formatters, query, api] import ".."/[types, formatters, query, api, redis_cache]
import ../views/[general, profile, timeline, status, search, mastoapi] import ../views/[general, profile, timeline, status, search, mastoapi]
export vdom export vdom
export uri, sequtils, json export uri, sequtils, json
export router_utils export router_utils
export formatters, query, api export formatters, query, api, redis_cache
export profile, timeline, status, mastoapi export profile, timeline, status, mastoapi
proc getQuery*(request: Request; tab, name: string): Query = proc getQuery*(request: Request; tab, name: string): Query =
@ -28,24 +28,11 @@ template skipIf[T](cond: bool; default; body: Future[T]): Future[T] =
else: else:
body body
proc getUserId(username: string): Future[string] {.async.} =
let user = await getGraphUser(username)
if user.suspended:
return "suspended"
else:
return user.id
proc getUsername*(userId: string): Future[string] {.async.} =
let user = await getGraphUserById(userId)
result = user.username
proc fetchProfile*(after: string; query: Query; cfg: Config; skipRail=false; proc fetchProfile*(after: string; query: Query; cfg: Config; skipRail=false;
skipPinned=false): Future[Profile] {.async.} = skipPinned=false): Future[Profile] {.async.} =
let let
name = query.fromUser[0] name = query.fromUser[0]
userId = await getUserId(name) userId = await getCachedUserId(name)
if userId.len == 0: if userId.len == 0:
return Profile(user: User(username: name)) return Profile(user: User(username: name))
@ -61,9 +48,9 @@ proc fetchProfile*(after: string; query: Query; cfg: Config; skipRail=false;
let let
rail = rail =
skipIf(skipRail or query.kind == media, @[]): skipIf(skipRail or query.kind == media, @[]):
getPhotoRail(name) getCachedPhotoRail(name)
user = getGraphUser(name) user = getCachedUser(name)
result = result =
case query.kind case query.kind
@ -107,7 +94,7 @@ template respTimeline*(timeline: typed) =
template respUserId*() = template respUserId*() =
cond @"user_id".len > 0 cond @"user_id".len > 0
let username = await getUsername(@"user_id") let username = await getCachedUsername(@"user_id")
if username.len > 0: if username.len > 0:
redirect("/" & username) redirect("/" & username)
else: else:
@ -133,17 +120,17 @@ proc createTimelineRouter*(cfg: Config) =
case tab: case tab:
of "followers": of "followers":
resp renderMain(renderUserList(await getGraphFollowers(await getUserId(@"name"), getCursor()), prefs), request, cfg, prefs) resp renderMain(renderUserList(await getGraphFollowers(await getCachedUserId(@"name"), getCursor()), prefs), request, cfg, prefs)
of "following": of "following":
resp renderMain(renderUserList(await getGraphFollowing(await getUserId(@"name"), getCursor()), prefs), request, cfg, prefs) resp renderMain(renderUserList(await getGraphFollowing(await getCachedUserId(@"name"), getCursor()), prefs), request, cfg, prefs)
else: else:
if request.headers.hasKey("Accept") and request.headers["Accept"] == "application/activity+json": if request.headers.hasKey("Accept") and request.headers["Accept"] == "application/activity+json":
let userId = await getUserId(@"name") let userId = await getCachedUserId(@"name")
if userId == "suspended" or userId.len == 0: if userId == "suspended" or userId.len == 0:
resp Http404, {"Content-Type": "application/json"}, """{"error":"User not found"}""" resp Http404, {"Content-Type": "application/json"}, """{"error":"User not found"}"""
let user = await getGraphUser(@"name") let user = await getCachedUser(@"name")
let userJson = getActivityStream(user, cfg, prefs) let userJson = getActivityStream(user, cfg, prefs)

View File

@ -4,7 +4,7 @@ import json, asyncdispatch, options, uri
import times import times
import jester import jester
import router_utils import router_utils
import ".."/[types, api, apiutils, query, consts] import ".."/[types, api, apiutils, query, consts, redis_cache]
import httpclient, strutils import httpclient, strutils
import sequtils import sequtils
@ -52,7 +52,7 @@ proc tweetToJson*(t: Tweet): JsonNode =
result["photos"] = %t.photos result["photos"] = %t.photos
proc getUserProfileJson*(username: string): Future[JsonNode] {.async.} = proc getUserProfileJson*(username: string): Future[JsonNode] {.async.} =
let user: User = await getGraphUser(username) let user: User = await getCachedUser(username)
let response: JsonNode = %*{ let response: JsonNode = %*{
"id": user.id, "id": user.id,
"username": user.username "username": user.username

View File

@ -89,7 +89,7 @@ type
bio*: string bio*: string
userPic*: string userPic*: string
banner*: string banner*: string
pinnedTweet*: int64 pinnedTweet*: string
following*: int following*: int
followers*: int followers*: int
tweets*: int tweets*: int
@ -203,14 +203,14 @@ type
quotes*: int quotes*: int
BirdwatchNote* = ref object BirdwatchNote* = ref object
id*: int64 id*: string
title*: string title*: string
text*: string text*: string
Tweet* = ref object Tweet* = ref object
id*: int64 id*: string
threadId*: int64 threadId*: string
replyId*: int64 replyId*: string
user*: User user*: User
text*: string text*: string
time*: DateTime time*: DateTime

View File

@ -29,8 +29,8 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
tdiv(class="nav-item right"): tdiv(class="nav-item right"):
icon "search", title="Search", href="/search" icon "search", title="Search", href="/search"
#if cfg.enableRss and rss.len > 0: if cfg.enableRss and rss.len > 0:
#icon "rss-feed", title="RSS Feed", href=rss icon "rss-feed", title="RSS Feed", href=rss
icon "bird", title="Open in Twitter", href=canonical icon "bird", title="Open in Twitter", href=canonical
a(href="https://liberapay.com/zedeus"): verbatim lp a(href="https://liberapay.com/zedeus"): verbatim lp
icon "info", title="About", href="/about" icon "info", title="About", href="/about"
@ -75,8 +75,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
if canonical.len > 0: if canonical.len > 0:
link(rel="canonical", href=canonical) link(rel="canonical", href=canonical)
#if cfg.enableRss and rss.len > 0: if cfg.enableRss and rss.len > 0:
#link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed") link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
if prefs.hlsPlayback: if prefs.hlsPlayback:
script(src="/js/hls.light.min.js", `defer`="") script(src="/js/hls.light.min.js", `defer`="")

View File

@ -116,7 +116,7 @@ proc getActivityStream*(tweet: Tweet, cfg: Config, prefs: Prefs): JsonNode =
postJson["id"] = %tweetUrl postJson["id"] = %tweetUrl
postJson["type"] = %"Note" postJson["type"] = %"Note"
postJson["summary"] = newJNull() postJson["summary"] = newJNull()
if tweet.replyId != 0: if tweet.replyId.len != 0:
let replyUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}" let replyUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
postJson["inReplyTo"] = %replyUrl postJson["inReplyTo"] = %replyUrl
postJson["inReplyToAtomUri"] = %replyUrl postJson["inReplyToAtomUri"] = %replyUrl

View File

@ -35,7 +35,7 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
<p>${text.replace("\n", "<br>\n")}</p> <p>${text.replace("\n", "<br>\n")}</p>
#if tweet.photos.len > 0: #if tweet.photos.len > 0:
# for photo in tweet.photos: # for photo in tweet.photos:
<img src="${urlPrefix}${getPicUrl(photo)}" style="max-width:250px;" /> <img src="${urlPrefix}${getPicUrl(photo.url)}" style="max-width:250px;" />
# end for # end for
#elif tweet.video.isSome: #elif tweet.video.isSome:
<img src="${urlPrefix}${getPicUrl(get(tweet.video).thumb)}" style="max-width:250px;" /> <img src="${urlPrefix}${getPicUrl(get(tweet.video).thumb)}" style="max-width:250px;" />
@ -44,11 +44,11 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
# let url = &"{urlPrefix}{getPicUrl(get(tweet.gif).url)}" # let url = &"{urlPrefix}{getPicUrl(get(tweet.gif).url)}"
<video poster="${thumb}" autoplay muted loop style="max-width:250px;"> <video poster="${thumb}" autoplay muted loop style="max-width:250px;">
<source src="${url}" type="video/mp4"></video> <source src="${url}" type="video/mp4"></video>
#elif tweet.card.isSome: ##elif tweet.card.isSome:
# let card = tweet.card.get() ## let card = tweet.card.get()
# if card.image.len > 0: ## if card.image.len > 0:
<img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" /> ##<img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" />
# end if ## end if
#end if #end if
#if tweet.quote.isSome and get(tweet.quote).available: #if tweet.quote.isSome and get(tweet.quote).available:
# let quoteLink = getLink(get(tweet.quote)) # let quoteLink = getLink(get(tweet.quote))

View File

@ -45,7 +45,7 @@ proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode
if conv.before.content.len > 0: if conv.before.content.len > 0:
tdiv(class="before-tweet thread-line"): tdiv(class="before-tweet thread-line"):
let first = conv.before.content[0] let first = conv.before.content[0]
if threadId != first.id and (first.replyId > 0 or not first.available): if threadId != first.id and (first.replyId.len > 0 or not first.available):
renderEarlier(conv.before) renderEarlier(conv.before)
for i, tweet in conv.before.content: for i, tweet in conv.before.content:
renderTweet(tweet, prefs, path, index=i) renderTweet(tweet, prefs, path, index=i)

View File

@ -55,7 +55,7 @@ proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode =
renderTweet(tweet, prefs, path, class=(header & "thread"), renderTweet(tweet, prefs, path, class=(header & "thread"),
index=i, last=(i == thread.high), showThread=show) index=i, last=(i == thread.high), showThread=show)
proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet): seq[Tweet] = proc threadFilter(tweets: openArray[Tweet]; threads: openArray[string]; it: Tweet): seq[Tweet] =
result = @[it] result = @[it]
if it.retweet.isSome or it.replyId in threads: return if it.retweet.isSome or it.replyId in threads: return
for t in tweets: for t in tweets:
@ -112,20 +112,20 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string;
else: else:
renderNoneFound() renderNoneFound()
else: else:
var retweets: seq[int64] var retweets: seq[string]
for thread in results.content: for thread in results.content:
if thread.len == 1: if thread.len == 1:
let let
tweet = thread[0] tweet = thread[0]
retweetId = if tweet.retweet.isSome: get(tweet.retweet).id else: 0 retweetId = if tweet.retweet.isSome: get(tweet.retweet).id else: ""
if retweetId in retweets or tweet.id in retweets or if retweetId in retweets or tweet.id in retweets or
tweet.pinned and prefs.hidePins: tweet.pinned and prefs.hidePins:
continue continue
var hasThread = tweet.hasThread var hasThread = tweet.hasThread
if retweetId != 0 and tweet.retweet.isSome: if retweetId.len != 0 and tweet.retweet.isSome:
retweets &= retweetId retweets &= retweetId
hasThread = get(tweet.retweet).hasThread hasThread = get(tweet.retweet).hasThread
renderTweet(tweet, prefs, path, showThread=hasThread) renderTweet(tweet, prefs, path, showThread=hasThread)