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)
* 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)
* No dependency on Redis, as it has caused ratelimiting issues, but also forcably disables RSS
## Why use Nitter?

View File

@ -95,7 +95,7 @@ proc getGraphTweetResult*(id: string): Future[Tweet] {.async.} =
js = await fetch(graphTweetResult ? params, Api.tweetResult)
result = parseGraphTweetResult(js)
proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} =
proc getGraphTweet*(id: string; after=""): Future[Conversation] {.async.} =
if id.len == 0: return
let
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:
result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0])
result.pinnedTweet = raw.pinnedTweetIdsStr[0]
result.expandUserEntities(raw)

View File

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

View File

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

View File

@ -205,9 +205,9 @@ proc parseCard(js: JsonNode; urls: JsonNode): Card =
proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
if js.isNull: return
result = Tweet(
id: js{"id_str"}.getId,
threadId: js{"conversation_id_str"}.getId,
replyId: js{"in_reply_to_status_id_str"}.getId,
id: js{"id_str"}.getStr,
threadId: js{"conversation_id_str"}.getStr,
replyId: js{"in_reply_to_status_id_str"}.getStr,
replyHandle: js{"in_reply_to_screen_name"}.getStr,
text: js{"full_text"}.getStr,
time: js{"created_at"}.getTime,
@ -223,17 +223,17 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
)
# fix for pinned threads
if result.hasThread and result.threadId == 0:
result.threadId = js{"self_thread", "id_str"}.getId
if result.hasThread and result.threadId.len == 0:
result.threadId = js{"self_thread", "id_str"}.getStr
if "retweeted_status" in js:
result.retweet = some Tweet()
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
with rt, js{"retweeted_status_id_str"}:
result.retweet = some Tweet(id: rt.getId)
result.retweet = some Tweet(id: rt.getStr)
return
# graphql
@ -324,11 +324,10 @@ proc parseTweetSearch*(js: JsonNode; after=""): Timeline =
result.content.add @[parsed]
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 =
let intId = if id.len > 0: parseBiggestInt(id) else: 0
result = global.tweets.getOrDefault(id, Tweet(id: intId))
result = global.tweets.getOrDefault(id, Tweet(id: id))
if result.quote.isSome:
let quote = get(result.quote).id
@ -463,7 +462,7 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
jsCard["binding_values"] = values
result = parseTweet(js{"legacy"}, jsCard)
result.id = js{"rest_id"}.getId
result.id = js{"rest_id"}.getStr
result.user = parseGraphUser(js{"core"})
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"}:
let note = BirdwatchNote(
id: communityNote{"note", "rest_id"}.getId,
id: communityNote{"note", "rest_id"}.getStr,
title: communityNote{"title"}.getStr,
)
note.expandBirdwatchEntities(communityNote{"subtitle"})
@ -519,16 +518,16 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation =
let tweet = parseGraphTweet(tweetResult, true)
if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId())
tweet.id = entryId.getId()
if $tweet.id == tweetId:
if tweet.id == tweetId:
result.tweet = tweet
else:
result.before.content.add tweet
elif entryId.startsWith("tombstone"):
let id = entryId.getId()
let tweet = Tweet(
id: parseBiggestInt(id),
id: id,
available: false,
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"}:
let tweet = parseGraphTweet(tweetResult, false)
if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId())
tweet.id = entryId.getId()
result.tweets.content.add tweet
elif "-conversation-" in entryId or entryId.startsWith("homeConversation"):
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"}:
let tweet = parseGraphTweet(tweetResult, false)
if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId())
tweet.id = entryId.getId()
result.tweets.content.add tweet
elif "-conversation-" in entryId or entryId.startsWith("homeConversation"):
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:
let entryId = i{"entry", "entryId"}.getEntryId
if entryId.len > 0:
tweet.id = parseBiggestInt(entryId)
tweet.id = entryId
result.pinned = some tweet
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"}:
let tweet = parseGraphTweet(tweetRes)
if not tweet.available:
tweet.id = parseBiggestInt(entryId.getId())
tweet.id = entryId.getId()
result.content.add tweet
elif T is 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)
if idx > -1 and name != replyTo:
tweet.reply.delete idx
elif idx == -1 and tweet.replyId != 0:
elif idx == -1 and tweet.replyId.len != 0:
tweet.reply.add name
replacements.deduplicate
@ -303,7 +303,7 @@ proc expandTweetEntities*(tweet: Tweet; js: JsonNode) =
hasJobCard = tweet.card.isSome and get(tweet.card).kind == jobDetails
var replyTo = ""
if tweet.replyId != 0:
if tweet.replyId.len != 0:
with reply, js{"in_reply_to_screen_name"}:
replyTo = reply.getStr
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 userKey(name: string): string = "p:" & name
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.} =
pool.withAcquire(r):
@ -96,10 +97,15 @@ proc cache*(data: User) {.async.} =
dawait r.setEx(name.userKey, baseCacheTime, compress(toFlatty(data)))
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):
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.} =
let key = "rss:" & query
pool.withAcquire(r):
@ -114,7 +120,13 @@ template deserialize(data, T) =
except:
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)
pool.withAcquire(r):
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:
await all(cacheUserId(result, user.id), cache(user))
# proc getCachedTweet*(id: int64): Future[Tweet] {.async.} =
# if id == 0: return
# let tweet = await get(id.tweetKey)
# if tweet != redisNil:
# tweet.deserialize(Tweet)
# else:
# result = await getGraphTweetResult($id)
# if not result.isNil:
# await cache(result)
proc getCachedTweet*(id: string; after=""): Future[Conversation] {.async.} =
if id.len == 0: return
let tweet = await get(id.tweetKey)
if tweet != redisNil:
result = deserializeConversation(tweet)
else:
result = await getGraphTweet(id)
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.} =
if name.len == 0: return

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import strutils, strformat, uri
import jester
import router_utils
import ".."/[types, api]
import ".."/[types, api, redis_cache]
import ../views/[general, timeline, list]
template respList*(list, timeline, title, vnode: typed) =
@ -36,7 +36,7 @@ proc createListRouter*(cfg: Config) =
cond @"slug" != "memberships"
let
slug = decodeUrl(@"slug")
list = await getList(@"name", slug)
list = await getCachedList(@"name", slug)
if list.id.len == 0:
resp Http404, showError(&"""List "{@"slug"}" not found""", cfg)
redirect(&"/i/lists/{list.id}")
@ -45,7 +45,7 @@ proc createListRouter*(cfg: Config) =
cond '.' notin @"id"
let
prefs = cookiePrefs()
list = await getList(id=(@"id"))
list = await getCachedList(id=(@"id"))
timeline = await getGraphListTweets(list.id, getCursor())
vnode = renderTimelineTweets(timeline, prefs, request.path)
respList(list, timeline, list.title, vnode)
@ -54,6 +54,6 @@ proc createListRouter*(cfg: Config) =
cond '.' notin @"id"
let
prefs = cookiePrefs()
list = await getList(id=(@"id"))
list = await getCachedList(id=(@"id"))
members = await getGraphListMembers(list, getCursor())
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 router_utils
import ".."/[types, formatters, api]
import ".."/[types, formatters, api, redis_cache]
import ../views/[general, status, search, mastoapi]
export json, uri, sequtils, options, sugar, times
@ -51,11 +51,11 @@ proc createStatusRouter*(cfg: Config) =
let prefs = cookiePrefs()
let conv = await getTweet(id)
let conv = await getCachedTweet(id)
if conv == nil:
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"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone
@ -81,11 +81,11 @@ proc createStatusRouter*(cfg: Config) =
resp Http404, ""
resp $renderReplies(replies, prefs, getPath())
let conv = await getTweet(id, getCursor())
let conv = await getCachedTweet(id, getCursor())
if conv == nil:
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"
if conv != nil and conv.tweet != nil and conv.tweet.tombstone.len > 0:
error = conv.tweet.tombstone
@ -109,15 +109,15 @@ proc createStatusRouter*(cfg: Config) =
let
quote = get(tweet.quote)
quoteUser = quote.user
if tweet.replyId != 0:
let replyUser = await getGraphUser(tweet.replyHandle)
if tweet.replyId.len != 0:
let replyUser = await getCachedUser(tweet.replyHandle)
context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})\n↘ {quoteUser.fullname} (@{quoteUser.username})"
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
else:
context = &"↘ {quoteUser.fullname} (@{quoteUser.username})"
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{quote.id}"
elif tweet.replyId != 0:
let replyUser = await getGraphUser(tweet.replyHandle)
elif tweet.replyId.len != 0:
let replyUser = await getCachedUser(tweet.replyHandle)
context = &"↩ {replyUser.fullname} (@{tweet.replyHandle})"
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 router_utils
import ".."/[types, formatters, query, api]
import ".."/[types, formatters, query, api, redis_cache]
import ../views/[general, profile, timeline, status, search, mastoapi]
export vdom
export uri, sequtils, json
export router_utils
export formatters, query, api
export formatters, query, api, redis_cache
export profile, timeline, status, mastoapi
proc getQuery*(request: Request; tab, name: string): Query =
@ -28,24 +28,11 @@ template skipIf[T](cond: bool; default; body: Future[T]): Future[T] =
else:
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;
skipPinned=false): Future[Profile] {.async.} =
let
name = query.fromUser[0]
userId = await getUserId(name)
userId = await getCachedUserId(name)
if userId.len == 0:
return Profile(user: User(username: name))
@ -61,9 +48,9 @@ proc fetchProfile*(after: string; query: Query; cfg: Config; skipRail=false;
let
rail =
skipIf(skipRail or query.kind == media, @[]):
getPhotoRail(name)
getCachedPhotoRail(name)
user = getGraphUser(name)
user = getCachedUser(name)
result =
case query.kind
@ -107,7 +94,7 @@ template respTimeline*(timeline: typed) =
template respUserId*() =
cond @"user_id".len > 0
let username = await getUsername(@"user_id")
let username = await getCachedUsername(@"user_id")
if username.len > 0:
redirect("/" & username)
else:
@ -133,17 +120,17 @@ proc createTimelineRouter*(cfg: Config) =
case tab:
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":
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:
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:
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)

View File

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

View File

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

View File

@ -29,8 +29,8 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
tdiv(class="nav-item right"):
icon "search", title="Search", href="/search"
#if cfg.enableRss and rss.len > 0:
#icon "rss-feed", title="RSS Feed", href=rss
if cfg.enableRss and rss.len > 0:
icon "rss-feed", title="RSS Feed", href=rss
icon "bird", title="Open in Twitter", href=canonical
a(href="https://liberapay.com/zedeus"): verbatim lp
icon "info", title="About", href="/about"
@ -75,8 +75,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
if canonical.len > 0:
link(rel="canonical", href=canonical)
#if cfg.enableRss and rss.len > 0:
#link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
if cfg.enableRss and rss.len > 0:
link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
if prefs.hlsPlayback:
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["type"] = %"Note"
postJson["summary"] = newJNull()
if tweet.replyId != 0:
if tweet.replyId.len != 0:
let replyUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
postJson["inReplyTo"] = %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>
#if tweet.photos.len > 0:
# 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
#elif tweet.video.isSome:
<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)}"
<video poster="${thumb}" autoplay muted loop style="max-width:250px;">
<source src="${url}" type="video/mp4"></video>
#elif tweet.card.isSome:
# let card = tweet.card.get()
# if card.image.len > 0:
<img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" />
# end if
##elif tweet.card.isSome:
## let card = tweet.card.get()
## if card.image.len > 0:
##<img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" />
## end if
#end if
#if tweet.quote.isSome and get(tweet.quote).available:
# 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:
tdiv(class="before-tweet thread-line"):
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)
for i, tweet in conv.before.content:
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"),
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]
if it.retweet.isSome or it.replyId in threads: return
for t in tweets:
@ -112,20 +112,20 @@ proc renderTimelineTweets*(results: Timeline; prefs: Prefs; path: string;
else:
renderNoneFound()
else:
var retweets: seq[int64]
var retweets: seq[string]
for thread in results.content:
if thread.len == 1:
let
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
tweet.pinned and prefs.hidePins:
continue
var hasThread = tweet.hasThread
if retweetId != 0 and tweet.retweet.isSome:
if retweetId.len != 0 and tweet.retweet.isSome:
retweets &= retweetId
hasThread = get(tweet.retweet).hasThread
renderTweet(tweet, prefs, path, showThread=hasThread)