Compare commits
2 commits
9129820cb0
...
1cf37e4e84
Author | SHA1 | Date | |
---|---|---|---|
1cf37e4e84 | |||
396322772f |
6 changed files with 87 additions and 31 deletions
|
@ -4,6 +4,9 @@ Nitter is a free and open source alternative Twitter front-end focused on
|
||||||
privacy and performance. The source is available on GitHub at
|
privacy and performance. The source is available on GitHub at
|
||||||
<https://github.com/zedeus/nitter>
|
<https://github.com/zedeus/nitter>
|
||||||
|
|
||||||
|
**This instance is running a fork, whose source can be found at**
|
||||||
|
<https://gitdab.com/Cynosphere/nitter>
|
||||||
|
|
||||||
* No JavaScript or ads
|
* No JavaScript or ads
|
||||||
* All requests go through the backend, client never talks to Twitter
|
* All requests go through the backend, client never talks to Twitter
|
||||||
* Prevents Twitter from tracking your IP or JavaScript fingerprint
|
* Prevents Twitter from tracking your IP or JavaScript fingerprint
|
||||||
|
@ -19,6 +22,13 @@ Nitter's GitHub wiki contains
|
||||||
[browser extensions](https://github.com/zedeus/nitter/wiki/Extensions)
|
[browser extensions](https://github.com/zedeus/nitter/wiki/Extensions)
|
||||||
maintained by the community.
|
maintained by the community.
|
||||||
|
|
||||||
|
### Fork features
|
||||||
|
|
||||||
|
* Localized following via cookies (list exportable and editable in preferences)
|
||||||
|
* 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)
|
||||||
|
|
||||||
## Why use Nitter?
|
## Why use Nitter?
|
||||||
|
|
||||||
It's impossible to use Twitter without JavaScript enabled. For privacy-minded
|
It's impossible to use Twitter without JavaScript enabled. For privacy-minded
|
||||||
|
@ -36,12 +46,12 @@ Twitter without JavaScript while retaining your privacy. In addition to
|
||||||
respecting your privacy, Nitter is on average around 15 times lighter than
|
respecting your privacy, Nitter is on average around 15 times lighter than
|
||||||
Twitter, and in most cases serves pages faster (eg. timelines load 2-4x faster).
|
Twitter, and in most cases serves pages faster (eg. timelines load 2-4x faster).
|
||||||
|
|
||||||
In the future a simple account system will be added that lets you follow Twitter
|
|
||||||
users, allowing you to have a clean chronological timeline without needing a
|
|
||||||
Twitter account.
|
|
||||||
|
|
||||||
## Donating
|
## Donating
|
||||||
|
|
||||||
|
Even though I could be selfish and point people to donate to me instead of
|
||||||
|
Zedeus, it would be disrespectful.
|
||||||
|
|
||||||
|
GitHub Sponsors: <https://github.com/sponsors/zedeus> \
|
||||||
Liberapay: <https://liberapay.com/zedeus> \
|
Liberapay: <https://liberapay.com/zedeus> \
|
||||||
Patreon: <https://patreon.com/nitter> \
|
Patreon: <https://patreon.com/nitter> \
|
||||||
BTC: bc1qp7q4qz0fgfvftm5hwz3vy284nue6jedt44kxya \
|
BTC: bc1qp7q4qz0fgfvftm5hwz3vy284nue6jedt44kxya \
|
||||||
|
@ -49,6 +59,23 @@ ETH: 0x66d84bc3fd031b62857ad18c62f1ba072b011925 \
|
||||||
LTC: ltc1qhsz5nxw6jw9rdtw9qssjeq2h8hqk2f85rdgpkr \
|
LTC: ltc1qhsz5nxw6jw9rdtw9qssjeq2h8hqk2f85rdgpkr \
|
||||||
XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL
|
XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL
|
||||||
|
|
||||||
## Contact
|
## Credits
|
||||||
|
|
||||||
Feel free to join our [Matrix channel](https://matrix.to/#/#nitter:matrix.org).
|
* Zedeus for this project
|
||||||
|
* PrivacyDevel, cmj, and taskylizard for keeping this project alive with forks after the main repo went inactive
|
||||||
|
* Every other contributors who've committed to the main repo in the past
|
||||||
|
|
||||||
|
## To any law enforcement agencies and copyright holders
|
||||||
|
|
||||||
|
**All illegal content should be reported to Twitter directly.** This service is
|
||||||
|
merely a proxy of Twitter and no content is hosted on this server. Do not waste
|
||||||
|
your time contacting internet service providers, hosting providers and/or domain
|
||||||
|
registrars.
|
||||||
|
|
||||||
|
If you would like more context, you can read about this exact issue happening to
|
||||||
|
[PussTheCat.org's instance](https://pussthecat.org/nitter/).
|
||||||
|
|
||||||
|
I emplore all Nitter instance hosts to not enable media proxying, even if it
|
||||||
|
"phones home" to Twitter's CDN (which doesn't really pose a tracking risk and
|
||||||
|
breaks videos anyways), as it [has been used as an attack vector to take down
|
||||||
|
nitter.net](https://github.com/zedeus/nitter/issues/1150#issuecomment-1890855255).
|
||||||
|
|
|
@ -208,6 +208,7 @@ proc parseTweet(js: JsonNode; jsCard: JsonNode = newJNull()): Tweet =
|
||||||
id: js{"id_str"}.getId,
|
id: js{"id_str"}.getId,
|
||||||
threadId: js{"conversation_id_str"}.getId,
|
threadId: js{"conversation_id_str"}.getId,
|
||||||
replyId: js{"in_reply_to_status_id_str"}.getId,
|
replyId: js{"in_reply_to_status_id_str"}.getId,
|
||||||
|
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,
|
||||||
hasThread: js{"self_thread"}.notNull,
|
hasThread: js{"self_thread"}.notNull,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import asyncdispatch, strutils, sequtils, uri, options, sugar
|
import asyncdispatch, strutils, sequtils, uri, options, sugar, strformat
|
||||||
|
|
||||||
import jester, karax/vdom
|
import jester, karax/vdom
|
||||||
|
|
||||||
|
@ -64,30 +64,43 @@ proc createStatusRouter*(cfg: Config) =
|
||||||
resp Http404, showError(error, cfg)
|
resp Http404, showError(error, cfg)
|
||||||
|
|
||||||
let
|
let
|
||||||
title = pageTitle(conv.tweet)
|
tweet = conv.tweet
|
||||||
ogTitle = pageTitle(conv.tweet.user)
|
title = pageTitle(tweet)
|
||||||
desc = conv.tweet.text
|
ogTitle = pageTitle(tweet.user)
|
||||||
avatar = conv.tweet.user.userPic
|
desc = tweet.text
|
||||||
time = some(conv.tweet.time)
|
avatar = tweet.user.userPic
|
||||||
|
time = some(tweet.time)
|
||||||
|
|
||||||
var
|
var
|
||||||
images = conv.tweet.photos
|
images = tweet.photos
|
||||||
video = ""
|
video = ""
|
||||||
|
context = ""
|
||||||
|
contextUrl = ""
|
||||||
|
|
||||||
if conv.tweet.video.isSome():
|
if tweet.quote.isSome():
|
||||||
let videoObj = get(conv.tweet.video)
|
let
|
||||||
|
quote = tweet.quote.get()
|
||||||
|
quoteUser = quote.user
|
||||||
|
context = &"↘ Quoting: {quoteUser.fullname} (@{quoteUser.username})"
|
||||||
|
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{quote.id}"
|
||||||
|
elif tweet.replyId != 0:
|
||||||
|
context = &"↩ Replying to: @{tweet.replyHandle}"
|
||||||
|
contextUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
|
||||||
|
|
||||||
|
if tweet.video.isSome():
|
||||||
|
let videoObj = get(tweet.video)
|
||||||
images = @[videoObj.thumb]
|
images = @[videoObj.thumb]
|
||||||
|
|
||||||
let vars = videoObj.variants.filterIt(it.contentType == mp4)
|
let vars = videoObj.variants.filterIt(it.contentType == mp4)
|
||||||
# idk why this wont sort when it sorts everywhere else
|
# idk why this wont sort when it sorts everywhere else
|
||||||
#video = vars.sortedByIt(it.bitrate)[^1].url
|
#video = vars.sortedByIt(it.bitrate)[^1].url
|
||||||
video = vars[^1].url
|
video = vars[^1].url
|
||||||
elif conv.tweet.gif.isSome():
|
elif tweet.gif.isSome():
|
||||||
let gif = get(conv.tweet.gif)
|
let gif = get(tweet.gif)
|
||||||
images = @[gif.thumb]
|
images = @[gif.thumb]
|
||||||
video = getPicUrl(gif.url)
|
video = getPicUrl(gif.url)
|
||||||
#elif conv.tweet.card.isSome():
|
#elif tweet.card.isSome():
|
||||||
# let card = conv.tweet.card.get()
|
# let card = tweet.card.get()
|
||||||
# if card.image.len > 0:
|
# if card.image.len > 0:
|
||||||
# images = @[card.image]
|
# images = @[card.image]
|
||||||
# elif card.video.isSome():
|
# elif card.video.isSome():
|
||||||
|
@ -95,7 +108,8 @@ proc createStatusRouter*(cfg: Config) =
|
||||||
|
|
||||||
let html = renderConversation(conv, prefs, getPath() & "#m")
|
let html = renderConversation(conv, prefs, getPath() & "#m")
|
||||||
resp renderMain(html, request, cfg, prefs, title, desc, ogTitle,
|
resp renderMain(html, request, cfg, prefs, title, desc, ogTitle,
|
||||||
images=images, video=video, avatar=avatar, time=time)
|
images=images, video=video, avatar=avatar, time=time,
|
||||||
|
context=context, contextUrl=contextUrl)
|
||||||
|
|
||||||
get "/@name/@s/@id/@m/?@i?":
|
get "/@name/@s/@id/@m/?@i?":
|
||||||
cond @"s" in ["status", "statuses"]
|
cond @"s" in ["status", "statuses"]
|
||||||
|
|
|
@ -211,6 +211,7 @@ type
|
||||||
text*: string
|
text*: string
|
||||||
time*: DateTime
|
time*: DateTime
|
||||||
reply*: seq[string]
|
reply*: seq[string]
|
||||||
|
replyHandle*: string
|
||||||
pinned*: bool
|
pinned*: bool
|
||||||
hasThread*: bool
|
hasThread*: bool
|
||||||
available*: bool
|
available*: bool
|
||||||
|
|
|
@ -5,7 +5,7 @@ import karax/[karaxdsl, vdom]
|
||||||
const
|
const
|
||||||
date = staticExec("git show -s --format=\"%cd\" --date=format:\"%Y.%m.%d\"")
|
date = staticExec("git show -s --format=\"%cd\" --date=format:\"%Y.%m.%d\"")
|
||||||
hash = staticExec("git show -s --format=\"%h\"")
|
hash = staticExec("git show -s --format=\"%h\"")
|
||||||
link = "https://github.com/zedeus/nitter/commit/" & hash
|
link = "https://gitdab.com/Cynosphere/nitter/commit/" & hash
|
||||||
version = &"{date}-{hash}"
|
version = &"{date}-{hash}"
|
||||||
|
|
||||||
var aboutHtml: string
|
var aboutHtml: string
|
||||||
|
|
|
@ -38,7 +38,7 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
|
||||||
|
|
||||||
proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
video=""; images: seq[string] = @[]; banner=""; ogTitle="";
|
video=""; images: seq[string] = @[]; banner=""; ogTitle="";
|
||||||
rss=""; canonical=""; avatar="";
|
rss=""; canonical=""; avatar=""; context=""; contextUrl="";
|
||||||
time: Option[DateTime] = none(DateTime)): VNode =
|
time: Option[DateTime] = none(DateTime)): VNode =
|
||||||
var theme = prefs.theme.toTheme
|
var theme = prefs.theme.toTheme
|
||||||
if "theme" in req.params:
|
if "theme" in req.params:
|
||||||
|
@ -140,16 +140,28 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
meta(property="og:video:url", content=video)
|
meta(property="og:video:url", content=video)
|
||||||
meta(property="og:video:secure_url", content=video)
|
meta(property="og:video:secure_url", content=video)
|
||||||
meta(property="og:video:type", content="video/mp4")
|
meta(property="og:video:type", content="video/mp4")
|
||||||
var title = encodeUrl(finalizedDesc)
|
|
||||||
var author = encodeUrl(finalizedTitleText)
|
var
|
||||||
|
title = encodeUrl(finalizedDesc)
|
||||||
|
author = encodeUrl(finalizedTitleText)
|
||||||
|
url = req.path
|
||||||
|
|
||||||
if len(finalizedDesc) > 67:
|
if len(finalizedDesc) > 67:
|
||||||
title = author
|
title = author
|
||||||
author = encodeUrl(finalizedDesc)
|
author = encodeUrl(finalizedDesc)
|
||||||
|
|
||||||
verbatim &"<link rel=\"alternate\" href=\"{getUrlPrefix(cfg)}/oembed.json?type=video&provider={encodeUrl(siteName)}&title={title}&user={author}&url={encodeUrl(req.path)}\" type=\"application/json+oembed\" />"
|
if context != "":
|
||||||
#link(rel="alternate",
|
author = encodeUrl(context & "\n") & author
|
||||||
# href=&"{getUrlPrefix(cfg)}/oembed.json?type=video&title={encodeUrl(stripHtml(desc))}&user={encodeUrl(finalizedTitleText)}&url={encodeUrl(req.path)}",
|
|
||||||
# `type`="application/json+oembed")
|
if contextUrl != "":
|
||||||
|
url = contextUrl
|
||||||
|
|
||||||
|
verbatim &"<link rel=\"alternate\" href=\"{getUrlPrefix(cfg)}/oembed.json?type=video&provider={encodeUrl(siteName)}&title={title}&user={author}&url={encodeUrl(url)}\" type=\"application/json+oembed\" />"
|
||||||
|
elif context != "" and contextUrl != "":
|
||||||
|
var
|
||||||
|
title = encodeUrl(finalizedTitleText)
|
||||||
|
author = encodeUrl(context)
|
||||||
|
verbatim &"<link rel=\"alternate\" href=\"{getUrlPrefix(cfg)}/oembed.json?type=video&provider={encodeUrl(siteName)}&title={title}&user={author}&url={encodeUrl(contextUrl)}\" type=\"application/json+oembed\" />"
|
||||||
|
|
||||||
# this is last so images are also preloaded
|
# this is last so images are also preloaded
|
||||||
# if this is done earlier, Chrome only preloads one image for some reason
|
# if this is done earlier, Chrome only preloads one image for some reason
|
||||||
|
@ -158,14 +170,15 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
|
|
||||||
proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs;
|
proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs;
|
||||||
titleText=""; desc=""; ogTitle=""; rss=""; video="";
|
titleText=""; desc=""; ogTitle=""; rss=""; video="";
|
||||||
images: seq[string] = @[]; banner=""; avatar="";
|
images: seq[string] = @[]; banner=""; avatar=""; context="";
|
||||||
time: Option[DateTime] = none(DateTime)): string =
|
contextUrl = ""; time: Option[DateTime] = none(DateTime)
|
||||||
|
): string =
|
||||||
|
|
||||||
let canonical = getTwitterLink(req.path, req.params)
|
let canonical = getTwitterLink(req.path, req.params)
|
||||||
|
|
||||||
let node = buildHtml(html(lang="en")):
|
let node = buildHtml(html(lang="en")):
|
||||||
renderHead(prefs, cfg, req, titleText, desc, video, images, banner, ogTitle,
|
renderHead(prefs, cfg, req, titleText, desc, video, images, banner, ogTitle,
|
||||||
rss, canonical, avatar, time)
|
rss, canonical, avatar, context, contextUrl, time)
|
||||||
|
|
||||||
body:
|
body:
|
||||||
renderNavbar(cfg, req, rss, canonical)
|
renderNavbar(cfg, req, rss, canonical)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue