Compare commits
2 Commits
c1534dc057
...
48b1d9e565
Author | SHA1 | Date | |
---|---|---|---|
48b1d9e565 | |||
96dad1e3a1 |
@ -10,17 +10,16 @@ RUN nimble install -y --depsOnly
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN nimble build -d:danger -d:lto -d:strip
|
RUN nimble build -d:danger -d:lto -d:strip
|
||||||
RUN nimble scss
|
RUN nimble scss
|
||||||
RUN nimble md
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM debian:unstable-slim
|
||||||
|
|
||||||
RUN apk --no-cache add pcre ca-certificates
|
RUN apk --no-cache add pcre ca-certificates
|
||||||
RUN useradd -d /src -u 1001 nitter
|
RUN useradd -d /src -u 1001 nitter
|
||||||
|
|
||||||
WORKDIR /srv
|
WORKDIR /srv
|
||||||
|
|
||||||
COPY --from=build /srv/nitter ./
|
COPY --from=build /src/nitter ./
|
||||||
COPY --from=build /srv/public ./public
|
COPY --from=build /src/public ./public
|
||||||
|
|
||||||
USER nitter
|
USER nitter
|
||||||
CMD ./nitter
|
CMD ./nitter
|
||||||
|
@ -15,7 +15,6 @@ requires "jester#baca3f"
|
|||||||
requires "karax#5cf360c"
|
requires "karax#5cf360c"
|
||||||
requires "sass#7dfdd03"
|
requires "sass#7dfdd03"
|
||||||
requires "nimcrypto#a079df9"
|
requires "nimcrypto#a079df9"
|
||||||
requires "markdown#158efe3"
|
|
||||||
requires "packedjson#9e6fbb6"
|
requires "packedjson#9e6fbb6"
|
||||||
requires "supersnappy#6c94198"
|
requires "supersnappy#6c94198"
|
||||||
requires "redpool#8b7c1db"
|
requires "redpool#8b7c1db"
|
||||||
@ -29,6 +28,3 @@ requires "oauth#b8c163b"
|
|||||||
|
|
||||||
task scss, "Generate css":
|
task scss, "Generate css":
|
||||||
exec "nimble c --hint[Processing]:off -d:danger -r tools/gencss"
|
exec "nimble c --hint[Processing]:off -d:danger -r tools/gencss"
|
||||||
|
|
||||||
task md, "Render md":
|
|
||||||
exec "nimble c --hint[Processing]:off -d:danger -r tools/rendermd"
|
|
||||||
|
5
public/js/hls.light.min.js
vendored
5
public/js/hls.light.min.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/hls.min.js
vendored
Normal file
2
public/js/hls.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,54 +0,0 @@
|
|||||||
# About
|
|
||||||
|
|
||||||
Nitter is a free and open source alternative Twitter front-end focused on
|
|
||||||
privacy and performance. The source is available on GitHub at
|
|
||||||
<https://github.com/zedeus/nitter>
|
|
||||||
|
|
||||||
* No JavaScript or ads
|
|
||||||
* All requests go through the backend, client never talks to Twitter
|
|
||||||
* Prevents Twitter from tracking your IP or JavaScript fingerprint
|
|
||||||
* Uses Twitter's unofficial API (no rate limits or developer account required)
|
|
||||||
* Lightweight (for [@nim_lang](/nim_lang), 60KB vs 784KB from twitter.com)
|
|
||||||
* RSS feeds
|
|
||||||
* Themes
|
|
||||||
* Mobile support (responsive design)
|
|
||||||
* AGPLv3 licensed, no proprietary instances permitted
|
|
||||||
|
|
||||||
Nitter's GitHub wiki contains
|
|
||||||
[instances](https://github.com/zedeus/nitter/wiki/Instances) and
|
|
||||||
[browser extensions](https://github.com/zedeus/nitter/wiki/Extensions)
|
|
||||||
maintained by the community.
|
|
||||||
|
|
||||||
## Why use Nitter?
|
|
||||||
|
|
||||||
It's impossible to use Twitter without JavaScript enabled. For privacy-minded
|
|
||||||
folks, preventing JavaScript analytics and IP-based tracking is important, but
|
|
||||||
apart from using a VPN and uBlock/uMatrix, it's impossible. Despite being behind
|
|
||||||
a VPN and using heavy-duty adblockers, you can get accurately tracked with your
|
|
||||||
[browser's fingerprint](https://restoreprivacy.com/browser-fingerprinting/),
|
|
||||||
[no JavaScript required](https://noscriptfingerprint.com/). This all became
|
|
||||||
particularly important after Twitter [removed the
|
|
||||||
ability](https://www.eff.org/deeplinks/2020/04/twitter-removes-privacy-option-and-shows-why-we-need-strong-privacy-laws)
|
|
||||||
for users to control whether their data gets sent to advertisers.
|
|
||||||
|
|
||||||
Using an instance of Nitter (hosted on a VPS for example), you can browse
|
|
||||||
Twitter without JavaScript while retaining your privacy. In addition to
|
|
||||||
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).
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Liberapay: <https://liberapay.com/zedeus> \
|
|
||||||
Patreon: <https://patreon.com/nitter> \
|
|
||||||
BTC: bc1qp7q4qz0fgfvftm5hwz3vy284nue6jedt44kxya \
|
|
||||||
ETH: 0x66d84bc3fd031b62857ad18c62f1ba072b011925 \
|
|
||||||
LTC: ltc1qhsz5nxw6jw9rdtw9qssjeq2h8hqk2f85rdgpkr \
|
|
||||||
XMR: 42hKayRoEAw4D6G6t8mQHPJHQcXqofjFuVfavqKeNMNUZfeJLJAcNU19i1bGdDvcdN6romiSscWGWJCczFLe9RFhM3d1zpL
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
Feel free to join our [Matrix channel](https://matrix.to/#/#nitter:matrix.org).
|
|
@ -35,7 +35,7 @@ proc shortLink*(text: string; length=28): string =
|
|||||||
result = text.replace(wwwRegex, "")
|
result = text.replace(wwwRegex, "")
|
||||||
if result.len > length:
|
if result.len > length:
|
||||||
result = result[0 ..< length] & "…"
|
result = result[0 ..< length] & "…"
|
||||||
|
|
||||||
proc stripHtml*(text: string; shorten=false): string =
|
proc stripHtml*(text: string; shorten=false): string =
|
||||||
var html = parseHtml(text)
|
var html = parseHtml(text)
|
||||||
for el in html.findAll("a"):
|
for el in html.findAll("a"):
|
||||||
@ -82,6 +82,8 @@ proc proxifyVideo*(manifest: string; proxy: bool): string =
|
|||||||
for line in manifest.splitLines:
|
for line in manifest.splitLines:
|
||||||
let url =
|
let url =
|
||||||
if line.startsWith("#EXT-X-MAP:URI"): line[16 .. ^2]
|
if line.startsWith("#EXT-X-MAP:URI"): line[16 .. ^2]
|
||||||
|
elif line.startsWith("#EXT-X-MEDIA") and "URI=" in line:
|
||||||
|
line[line.find("URI=") + 5 .. -1 + line.find("\"", start= 5 + line.find("URI="))]
|
||||||
else: line
|
else: line
|
||||||
if url.startsWith('/'):
|
if url.startsWith('/'):
|
||||||
let path = "https://video.twimg.com" & url
|
let path = "https://video.twimg.com" & url
|
||||||
|
@ -8,14 +8,11 @@ from os import getEnv
|
|||||||
import jester
|
import jester
|
||||||
|
|
||||||
import types, config, prefs, formatters, redis_cache, http_pool, auth
|
import types, config, prefs, formatters, redis_cache, http_pool, auth
|
||||||
import views/[general, about]
|
import views/[general]
|
||||||
import routes/[
|
import routes/[
|
||||||
preferences, timeline, status, media, search, rss, list, debug,
|
preferences, timeline, status, media, search, rss, list, debug,
|
||||||
unsupported, embed, resolver, router_utils]
|
unsupported, embed, resolver, router_utils]
|
||||||
|
|
||||||
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
|
|
||||||
const issuesUrl = "https://github.com/zedeus/nitter/issues"
|
|
||||||
|
|
||||||
initAccountPool(cfg)
|
initAccountPool(cfg)
|
||||||
|
|
||||||
if not cfg.enableDebug:
|
if not cfg.enableDebug:
|
||||||
@ -32,7 +29,6 @@ setHmacKey(cfg.hmacKey)
|
|||||||
setProxyEncoding(cfg.base64Media)
|
setProxyEncoding(cfg.base64Media)
|
||||||
setMaxHttpConns(cfg.httpMaxConns)
|
setMaxHttpConns(cfg.httpMaxConns)
|
||||||
setHttpProxy(cfg.proxy, cfg.proxyAuth)
|
setHttpProxy(cfg.proxy, cfg.proxyAuth)
|
||||||
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"
|
||||||
@ -60,15 +56,6 @@ routes:
|
|||||||
get "/":
|
get "/":
|
||||||
resp renderMain(renderSearch(), request, cfg, themePrefs())
|
resp renderMain(renderSearch(), request, cfg, themePrefs())
|
||||||
|
|
||||||
get "/about":
|
|
||||||
resp renderMain(renderAbout(), request, cfg, themePrefs())
|
|
||||||
|
|
||||||
get "/explore":
|
|
||||||
redirect("/about")
|
|
||||||
|
|
||||||
get "/help":
|
|
||||||
redirect("/about")
|
|
||||||
|
|
||||||
get "/i/redirect":
|
get "/i/redirect":
|
||||||
let url = decodeUrl(@"url")
|
let url = decodeUrl(@"url")
|
||||||
if url.len == 0: resp Http404
|
if url.len == 0: resp Http404
|
||||||
@ -79,18 +66,17 @@ routes:
|
|||||||
|
|
||||||
error InternalError:
|
error InternalError:
|
||||||
echo error.exc.name, ": ", error.exc.msg
|
echo error.exc.name, ": ", error.exc.msg
|
||||||
const link = a("open a GitHub issue", href = issuesUrl)
|
const link = a("https://git.ngn.tf/ngn/nitter", href = "https://git.ngn.tf/ngn/nitter")
|
||||||
resp Http500, showError(
|
resp Http500, showError(
|
||||||
&"An error occurred, please {link} with the URL you tried to visit.", cfg)
|
&"An error occurred, please report to {link}", cfg)
|
||||||
|
|
||||||
error BadClientError:
|
error BadClientError:
|
||||||
echo error.exc.name, ": ", error.exc.msg
|
echo error.exc.name, ": ", error.exc.msg
|
||||||
resp Http500, showError("Network error occurred, please try again.", cfg)
|
resp Http500, showError("Network error occurred, please try again.", cfg)
|
||||||
|
|
||||||
error RateLimitError:
|
error RateLimitError:
|
||||||
const link = a("another instance", href = instancesUrl)
|
|
||||||
resp Http429, showError(
|
resp Http429, showError(
|
||||||
&"Instance has been rate limited.<br>Use {link} or try again later.", cfg)
|
&"Instance has been rate limited.", cfg)
|
||||||
|
|
||||||
extend rss, ""
|
extend rss, ""
|
||||||
extend status, ""
|
extend status, ""
|
||||||
|
@ -12,11 +12,10 @@ proc createUnsupportedRouter*(cfg: Config) =
|
|||||||
template feature {.dirty.} =
|
template feature {.dirty.} =
|
||||||
resp renderMain(renderFeature(), request, cfg, themePrefs())
|
resp renderMain(renderFeature(), request, cfg, themePrefs())
|
||||||
|
|
||||||
get "/about/feature": feature()
|
|
||||||
get "/login/?@i?": feature()
|
get "/login/?@i?": feature()
|
||||||
get "/@name/lists/?": feature()
|
get "/@name/lists/?": feature()
|
||||||
|
|
||||||
get "/intent/?@i?":
|
get "/intent/?@i?":
|
||||||
cond @"i" notin ["user"]
|
cond @"i" notin ["user"]
|
||||||
feature()
|
feature()
|
||||||
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
import os, strformat
|
|
||||||
import karax/[karaxdsl, vdom]
|
|
||||||
|
|
||||||
const
|
|
||||||
date = staticExec("git show -s --format=\"%cd\" --date=format:\"%Y.%m.%d\"")
|
|
||||||
hash = staticExec("git show -s --format=\"%h\"")
|
|
||||||
link = "https://github.com/zedeus/nitter/commit/" & hash
|
|
||||||
version = &"{date}-{hash}"
|
|
||||||
|
|
||||||
var aboutHtml: string
|
|
||||||
|
|
||||||
proc initAboutPage*(dir: string) =
|
|
||||||
try:
|
|
||||||
aboutHtml = readFile(dir/"md/about.html")
|
|
||||||
except IOError:
|
|
||||||
stderr.write (dir/"md/about.html") & " not found, please run `nimble md`\n"
|
|
||||||
aboutHtml = "<h1>About page is missing</h1><br><br>"
|
|
||||||
|
|
||||||
proc renderAbout*(): VNode =
|
|
||||||
buildHtml(tdiv(class="overlay-panel")):
|
|
||||||
verbatim aboutHtml
|
|
||||||
h2: text "Instance info"
|
|
||||||
p:
|
|
||||||
text "Version "
|
|
||||||
a(href=link): text version
|
|
@ -6,9 +6,3 @@ proc renderFeature*(): VNode =
|
|||||||
h1: text "Unsupported feature"
|
h1: text "Unsupported feature"
|
||||||
p:
|
p:
|
||||||
text "Nitter doesn't support this feature yet, but it might in the future. "
|
text "Nitter doesn't support this feature yet, but it might in the future. "
|
||||||
text "You can check for an issue and open one if needed here: "
|
|
||||||
a(href="https://github.com/zedeus/nitter/issues"):
|
|
||||||
text "https://github.com/zedeus/nitter/issues"
|
|
||||||
p:
|
|
||||||
text "To find out more about the Nitter project, see the "
|
|
||||||
a(href="/about"): text "About page"
|
|
||||||
|
@ -31,9 +31,7 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
|
|||||||
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
|
|
||||||
a(href="https://liberapay.com/zedeus"): verbatim lp
|
a(href="https://liberapay.com/zedeus"): verbatim lp
|
||||||
icon "info", title="About", href="/about"
|
|
||||||
icon "cog", title="Preferences", href=("/settings?referer=" & encodeUrl(path))
|
icon "cog", title="Preferences", href=("/settings?referer=" & encodeUrl(path))
|
||||||
|
|
||||||
proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||||
@ -42,7 +40,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
|||||||
var theme = prefs.theme.toTheme
|
var theme = prefs.theme.toTheme
|
||||||
if "theme" in req.params:
|
if "theme" in req.params:
|
||||||
theme = req.params["theme"].toTheme
|
theme = req.params["theme"].toTheme
|
||||||
|
|
||||||
let ogType =
|
let ogType =
|
||||||
if video.len > 0: "video"
|
if video.len > 0: "video"
|
||||||
elif rss.len > 0: "object"
|
elif rss.len > 0: "object"
|
||||||
@ -73,7 +71,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
|||||||
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.min.js", `defer`="")
|
||||||
script(src="/js/hlsPlayback.js", `defer`="")
|
script(src="/js/hlsPlayback.js", `defer`="")
|
||||||
|
|
||||||
if prefs.infiniteScroll:
|
if prefs.infiniteScroll:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user