Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nitter.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ redisMaxConnections = 30
hmacKey = "secretkey" # random key for cryptographic signing of video urls
base64Media = false # use base64 encoding for proxied media urls
enableRSS = true # set this to false to disable RSS feeds
twitterLinkInRss = false # set this to true if you want to expose twitter links in rss feed instead nitter links
enableDebug = false # enable request logs and debug endpoints (/.sessions)
enableApi = false # enable api endpoints (/api/*/url)
proxy = "" # http/https url, SOCKS proxies are not supported
proxyAuth = ""

Expand Down
2 changes: 2 additions & 0 deletions src/config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ proc getConfig*(path: string): (Config, parseCfg.Config) =
base64Media: cfg.get("Config", "base64Media", false),
minTokens: cfg.get("Config", "tokenCount", 10),
enableRss: cfg.get("Config", "enableRSS", true),
twitterLinkInRss: cfg.get("Config", "twitterLinkInRss", false),
enableDebug: cfg.get("Config", "enableDebug", false),
enableApi: cfg.get("Config", "enableApi", false),
proxy: cfg.get("Config", "proxy", ""),
proxyAuth: cfg.get("Config", "proxyAuth", "")
)
Expand Down
4 changes: 3 additions & 1 deletion src/nitter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import types, config, prefs, formatters, redis_cache, http_pool, auth
import views/[general, about]
import routes/[
preferences, timeline, status, media, search, rss, list, debug,
unsupported, embed, resolver, router_utils]
unsupported, embed, resolver, router_utils, api]

const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
const issuesUrl = "https://github.com/zedeus/nitter/issues"
Expand Down Expand Up @@ -54,6 +54,7 @@ createMediaRouter(cfg)
createEmbedRouter(cfg)
createRssRouter(cfg)
createDebugRouter(cfg)
createApiRouter(cfg)

settings:
port = Port(cfg.port)
Expand Down Expand Up @@ -112,4 +113,5 @@ routes:
extend resolver, ""
extend embed, ""
extend debug, ""
extend api, ""
extend unsupported, ""
26 changes: 26 additions & 0 deletions src/routes/api.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: AGPL-3.0-only
import jester, uri, re
import ".."/[utils, types, formatters]

proc decode*(req: jester.Request; index: int): string =
decodeUrl(req.matches[index])

proc createApiRouter*(cfg: Config) =
router api:
get re"^\/api\/video\/(.+)":
cond cfg.enableApi
let link = decode(request, 0)

if link == nil:
resp Http404

redirect getVidUrl(link)

get re"^\/api\/pic\/(.+)":
cond cfg.enableApi
let link = decode(request, 0)

if link == nil:
resp Http404

redirect getPicUrl(link)
2 changes: 2 additions & 0 deletions src/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,9 @@ type
base64Media*: bool
minTokens*: int
enableRss*: bool
twitterLinkInRss*: bool
enableDebug*: bool
enableApi*: bool
proxy*: string
proxyAuth*: string

Expand Down
7 changes: 7 additions & 0 deletions src/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var
const
https* = "https://"
twimg* = "pbs.twimg.com/"
twitter* = "twitter.com"
nitterParams = ["name", "tab", "id", "list", "referer", "scroll"]
twitterDomains = @[
"twitter.com",
Expand Down Expand Up @@ -59,3 +60,9 @@ proc isTwitterUrl*(uri: Uri): bool =

proc isTwitterUrl*(url: string): bool =
isTwitterUrl(parseUri(url))

proc getTwitterPicUrl*(link: string) : string =
if link.startsWith(twimg):
&"{https}{link}"
else:
&"{https}{twimg}{link}"
83 changes: 61 additions & 22 deletions src/views/rss.nimf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#? stdtmpl(subsChar = '$', metaChar = '#')
## SPDX-License-Identifier: AGPL-3.0-only
#import strutils, xmltree, strformat, options, unicode
#import strutils, xmltree, strformat, options, unicode, algorithm
#import ../types, ../utils, ../formatters, ../prefs
#
#proc getTitle(tweet: Tweet; retweet: string): string =
Expand Down Expand Up @@ -30,34 +30,64 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#
#proc renderRssTweet(tweet: Tweet; cfg: Config): string =
#let tweet = tweet.retweet.get(tweet)
#let urlPrefix = getUrlPrefix(cfg)
#let twitterLinks = cfg.twitterLinkInRss
#let urlPrefix = if twitterLinks: &"{https}{twitter}"
# else: getUrlPrefix(cfg)
#let text = replaceUrls(tweet.text, defaultPrefs, absolute=urlPrefix)
<p>${text.replace("\n", "<br>\n")}</p>
#if tweet.quote.isSome and get(tweet.quote).available:
# let quoteLink = getLink(get(tweet.quote))
<p><a href="${urlPrefix}${quoteLink}">${cfg.hostname}${quoteLink}</a></p>
# let quoteLink = if twitterLinks: getLink(get(tweet.quote), false)
# else: getLink(get(tweet.quote))
<br><p><a id="quote-tweet" href="${urlPrefix}${quoteLink}">${urlPrefix}${quoteLink}</a></p>
#end if
#if tweet.photos.len > 0:
# for photo in tweet.photos:
<img src="${urlPrefix}${getPicUrl(photo)}" style="max-width:250px;" />
# let url = if twitterLinks: getTwitterPicUrl(photo)
# else: &"{urlPrefix}{getPicUrl(photo)}"
<img src="${url}" style="max-width:250px;" />
# end for
#elif tweet.video.isSome:
<img src="${urlPrefix}${getPicUrl(get(tweet.video).thumb)}" style="max-width:250px;" />
#elif tweet.gif.isSome:
# let thumb = &"{urlPrefix}{getPicUrl(get(tweet.gif).thumb)}"
# let url = &"{urlPrefix}{getPicUrl(get(tweet.gif).url)}"
#end if
#if tweet.video.isSome:
# let video = get(tweet.video)
# let thumb = if twitterLinks: getTwitterPicUrl(video.thumb)
# else: &"{urlPrefix}{getPicUrl(video.thumb)}"
# let isMp4 = video.variants.anyIt(it.contentType == mp4)
# let playbackType = if not defaultPrefs.proxyVideos and isMp4: mp4
# else: video.playbackType
# let vidUrl = video.variants.filterIt(it.contentType == playbackType).sortedByIt(it.resolution)[^1].url
# let url = if defaultPrefs.proxyVideos and not twitterLinks: getVidUrl(vidUrl)
# else: vidUrl
# if isMp4:
<video poster="${thumb}" style="max-width:250px;">
<source src="${url}" type="video/mp4">
</video>
# else:
<video poster="${thumb}" data-url="${url}" style="max-width:250px;"/>
# end if
#end if
#if tweet.gif.isSome:
# let gif = get(tweet.gif)
# let thumb = if twitterLinks: getTwitterPicUrl(gif.thumb)
# else: &"{urlPrefix}{getPicUrl(gif.thumb)}"
# let url = if twitterLinks: getTwitterPicUrl(gif.url)
# else: &"{urlPrefix}{getPicUrl(gif.url)}"
<video poster="${thumb}" autoplay muted loop style="max-width:250px;">
<source src="${url}" type="video/mp4"></video>
#elif tweet.card.isSome:
<source src="${url}" type="video/mp4">
</video>
#end if
#if tweet.card.isSome:
# let card = tweet.card.get()
# if card.image.len > 0:
<img src="${urlPrefix}${getPicUrl(card.image)}" style="max-width:250px;" />
# let url = if twitterLinks: getTwitterPicUrl(card.image)
# else: &"{urlPrefix}{getPicUrl(card.image)}"
<img src="${url}" style="max-width:250px;" />
# end if
#end if
#end proc
#
#proc renderRssTweets(tweets: seq[Tweets]; cfg: Config; userId=""): string =
#let urlPrefix = getUrlPrefix(cfg)
#let urlPrefix = if cfg.twitterLinkInRss: &"{https}{twitter}"
# else: getUrlPrefix(cfg)
#var links: seq[string]
#for thread in tweets:
# for tweet in thread:
Expand All @@ -66,7 +96,8 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#
# 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)
# let link = if cfg.twitterLinkInRss: getLink(tweet, false)
# else: getLink(tweet)
# if link in links: continue
# end if
# links.add link
Expand All @@ -83,17 +114,21 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#end proc
#
#proc renderTimelineRss*(profile: Profile; cfg: Config; multi=false): string =
#let urlPrefix = getUrlPrefix(cfg)
#let urlNitterPrefix = getUrlPrefix(cfg)
#let urlPrefix = if cfg.twitterLinkInRss: &"{https}{twitter}"
# else: urlNitterPrefix
#result = ""
#let handle = (if multi: "" else: "@") & profile.user.username
#let userPicUrl = if cfg.twitterLinkInRss: getTwitterPicUrl(profile.user.getUserPic(style="_400x400"))
# else: &"{urlPrefix}{getPicUrl(profile.user.getUserPic(style=\"_400x400\"))}"
#var title = profile.user.fullname
#if not multi: title &= " / " & handle
#end if
#title = xmltree.escape(title).sanitizeXml
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<atom:link href="${urlPrefix}/${profile.user.username}/rss" rel="self" type="application/rss+xml" />
<atom:link href="${urlNitterPrefix}/${profile.user.username}/rss" rel="self" type="application/rss+xml" />
<title>${title}</title>
<link>${urlPrefix}/${profile.user.username}</link>
<description>${getDescription(handle, cfg)}</description>
Expand All @@ -102,7 +137,7 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
<image>
<title>${title}</title>
<link>${urlPrefix}/${profile.user.username}</link>
<url>${urlPrefix}${getPicUrl(profile.user.getUserPic(style="_400x400"))}</url>
<url>${userPicUrl}</url>
<width>128</width>
<height>128</height>
</image>
Expand All @@ -114,12 +149,14 @@ ${renderRssTweets(profile.tweets.content, cfg, userId=profile.user.id)}
#end proc
#
#proc renderListRss*(tweets: seq[Tweets]; list: List; cfg: Config): string =
#let link = &"{getUrlPrefix(cfg)}/i/lists/{list.id}"
#let nitterLink = &"{getUrlPrefix(cfg)}/i/lists/{list.id}"
#let link = if cfg.twitterLinkInRss: &"{https}{twitter}/i/lists/{list.id}"
# else: nitterLink
#result = ""
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<atom:link href="${link}" rel="self" type="application/rss+xml" />
<atom:link href="${nitterLink}" rel="self" type="application/rss+xml" />
<title>${xmltree.escape(list.name)} / @${list.username}</title>
<link>${link}</link>
<description>${getDescription(&"{list.name} by @{list.username}", cfg)}</description>
Expand All @@ -131,13 +168,15 @@ ${renderRssTweets(tweets, cfg)}
#end proc
#
#proc renderSearchRss*(tweets: seq[Tweets]; name, param: string; cfg: Config): string =
#let link = &"{getUrlPrefix(cfg)}/search"
#let nitterLink = &"{getUrlPrefix(cfg)}/search"
#let link = if cfg.twitterLinkInRss: &"{https}{twitter}/search"
# else: nitterLink
#let escName = xmltree.escape(name)
#result = ""
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<atom:link href="${link}" rel="self" type="application/rss+xml" />
<atom:link href="${nitterLink}" rel="self" type="application/rss+xml" />
<title>Search results for "${escName}"</title>
<link>${link}</link>
<description>${getDescription(&"Search \"{escName}\"", cfg)}</description>
Expand Down