#? stdtmpl(subsChar = '$', metaChar = '#') ## SPDX-License-Identifier: AGPL-3.0-only #import strutils, sequtils, xmltree, strformat, options, unicode #import ../types, ../utils, ../formatters, ../prefs ## Snowflake ID cutoff for RSS GUID format transition ## Corresponds to approximately December 14, 2025 UTC #const guidCutoff = 2000000000000000000'i64 # #proc getTitle(tweet: Tweet; retweet: string): string = #var prefix = "" #if tweet.pinned: prefix = "Pinned: " #elif retweet.len > 0: prefix = &"RT by @{retweet}: " #elif tweet.reply.len > 0: prefix = &"R to @{tweet.reply[0]}: " #end if #var text = stripHtml(tweet.text) ##if unicode.runeLen(text) > 32: ## text = unicode.runeSubStr(text, 0, 32) & "..." ##end if #text = xmltree.escape(text) #if text.len > 0: # result = prefix & text # return #end if #if tweet.media.len > 0: # result = prefix # let firstKind = tweet.media[0].kind # if tweet.media.anyIt(it.kind != firstKind): # result &= "Media" # else: # case firstKind # of photoMedia: result &= "Image" # of videoMedia: result &= "Video" # of gifMedia: result &= "Gif" # end case # end if #end if #end proc # #proc getDescription(desc: string; cfg: Config): string = Twitter feed for: ${desc}. Generated by ${getUrlPrefix(cfg)} #end proc # #proc renderRssMedia(media: Media; tweet: Tweet; urlPrefix: string): string = #case media.kind #of photoMedia: # let photo = media.photo #of videoMedia: # let video = media.video
Video
#of gifMedia: # let gif = media.gif # let thumb = &"{urlPrefix}{getPicUrl(gif.thumb)}" # let url = &"{urlPrefix}{getPicUrl(gif.url)}" #end case #end proc # #proc getTweetsWithPinned(profile: Profile): seq[Tweets] = #result = profile.tweets.content #if profile.pinned.isSome and result.len > 0: # let pinnedTweet = profile.pinned.get # var inserted = false # for threadIdx in 0 ..< result.len: # if not inserted: # for tweetIdx in 0 ..< result[threadIdx].len: # if result[threadIdx][tweetIdx].id < pinnedTweet.id: # result[threadIdx].insert(pinnedTweet, tweetIdx) # inserted = true # end if # end for # end if # end for #end if #end proc # #proc renderRssTweet(tweet: Tweet; cfg: Config; prefs: Prefs): string = #let tweet = tweet.retweet.get(tweet) #let urlPrefix = getUrlPrefix(cfg) #let text = replaceUrls(tweet.text, prefs, absolute=urlPrefix)

${text.replace("\n", "
\n")}

#if tweet.media.len > 0: # for media in tweet.media: ${renderRssMedia(media, tweet, urlPrefix)} # end for #elif tweet.card.isSome: # let card = tweet.card.get() # if card.image.len > 0: # end if #end if #if tweet.note.len > 0 and not prefs.hideCommunityNotes:

Community note: ${replaceUrls(tweet.note, prefs, absolute=urlPrefix)}

#end if #if tweet.quote.isSome and get(tweet.quote).available: # let quoteTweet = get(tweet.quote) # let quoteLink = urlPrefix & getLink(quoteTweet)
${quoteTweet.user.fullname} (@${quoteTweet.user.username})

${renderRssTweet(quoteTweet, cfg, prefs)}

#end if #end proc # #proc renderRssTweets(tweets: seq[Tweets]; cfg: Config; prefs: Prefs; userId=""): string = #let urlPrefix = getUrlPrefix(cfg) #var links: seq[string] #for thread in tweets: # for tweet in thread: # if userId.len > 0 and tweet.user.id != userId: continue # end if # # let retweet = if tweet.retweet.isSome: tweet.user.username else: "" # let tweet = if retweet.len > 0: tweet.retweet.get else: tweet # let link = getLink(tweet) # if link in links: continue # end if # links.add link # let useGlobalGuid = tweet.id >= guidCutoff ${getTitle(tweet, retweet)} @${tweet.user.username} ${getRfc822Time(tweet)} #if useGlobalGuid: ${tweet.id} #else: ${urlPrefix & link} #end if ${urlPrefix & link} # end for #end for #end proc # #proc renderTimelineRss*(profile: Profile; cfg: Config; prefs: Prefs; multi=false): string = #let urlPrefix = getUrlPrefix(cfg) #result = "" #let handle = (if multi: "" else: "@") & profile.user.username #var title = profile.user.fullname #if not multi: title &= " / " & handle #end if #title = xmltree.escape(title).sanitizeXml ${title} ${urlPrefix}/${profile.user.username} ${getDescription(handle, cfg)} en-us 40 ${title} ${urlPrefix}/${profile.user.username} ${urlPrefix}${getPicUrl(profile.user.getUserPic(style="_400x400"))} 128 128 #let tweetsList = getTweetsWithPinned(profile) #if tweetsList.len > 0: ${renderRssTweets(tweetsList, cfg, prefs, userId=profile.user.id)} #end if #end proc # #proc renderListRss*(tweets: seq[Tweets]; list: List; cfg: Config; prefs: Prefs): string = #let link = &"{getUrlPrefix(cfg)}/i/lists/{list.id}" #result = "" ${xmltree.escape(list.name)} / @${list.username} ${link} ${getDescription(&"{list.name} by @{list.username}", cfg)} en-us 40 ${renderRssTweets(tweets, cfg, prefs)} #end proc # #proc renderSearchRss*(tweets: seq[Tweets]; name, param: string; cfg: Config; prefs: Prefs): string = #let link = &"{getUrlPrefix(cfg)}/search" #let escName = xmltree.escape(name) #result = "" Search results for "${escName}" ${link} ${getDescription(&"Search \"{escName}\"", cfg)} en-us 40 ${renderRssTweets(tweets, cfg, prefs)} #end proc