Skip to content

Commit 7f3426c

Browse files
Merge branch 'master' into feature-notes
2 parents b24a5da + 95893ee commit 7f3426c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+455
-235
lines changed

.github/workflows/build-docker.yml

+32-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI/CD
1+
name: Docker
22

33
on:
44
push:
@@ -8,31 +8,51 @@ on:
88
- master
99

1010
jobs:
11-
build-docker:
12-
runs-on: ubuntu-latest
11+
build-docker-amd64:
12+
runs-on: buildjet-2vcpu-ubuntu-2204
1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v3
1515
with:
1616
fetch-depth: 0
17-
- name: Set up QEMU
18-
uses: docker/setup-qemu-action@v1
19-
with:
20-
platforms: all
2117
- name: Set up Docker Buildx
2218
id: buildx
23-
uses: docker/setup-buildx-action@v1
19+
uses: docker/setup-buildx-action@v2
2420
with:
2521
version: latest
2622
- name: Login to DockerHub
27-
uses: docker/login-action@v1
23+
uses: docker/login-action@v2
2824
with:
2925
username: ${{ secrets.DOCKER_USERNAME }}
3026
password: ${{ secrets.DOCKER_PASSWORD }}
31-
- name: Build and push
32-
uses: docker/build-push-action@v2
27+
- name: Build and push AMD64 Docker image
28+
uses: docker/build-push-action@v3
3329
with:
3430
context: .
3531
file: ./Dockerfile
3632
platforms: linux/amd64
3733
push: true
3834
tags: zedeus/nitter:latest,zedeus/nitter:${{ github.sha }}
35+
build-docker-arm64:
36+
runs-on: buildjet-2vcpu-ubuntu-2204-arm
37+
steps:
38+
- uses: actions/checkout@v3
39+
with:
40+
fetch-depth: 0
41+
- name: Set up Docker Buildx
42+
id: buildx
43+
uses: docker/setup-buildx-action@v2
44+
with:
45+
version: latest
46+
- name: Login to DockerHub
47+
uses: docker/login-action@v2
48+
with:
49+
username: ${{ secrets.DOCKER_USERNAME }}
50+
password: ${{ secrets.DOCKER_PASSWORD }}
51+
- name: Build and push ARM64 Docker image
52+
uses: docker/build-push-action@v3
53+
with:
54+
context: .
55+
file: ./Dockerfile.arm64
56+
platforms: linux/arm64
57+
push: true
58+
tags: zedeus/nitter:latest-arm64,zedeus/nitter:${{ github.sha }}-arm64

.github/workflows/run-tests.yml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Run tests
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- "*.md"
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
with:
14+
fetch-depth: 0
15+
- name: Cache nimble
16+
id: cache-nimble
17+
uses: actions/cache@v3
18+
with:
19+
path: ~/.nimble
20+
key: nimble-${{ hashFiles('*.nimble') }}
21+
restore-keys: "nimble-"
22+
- uses: actions/setup-python@v4
23+
with:
24+
python-version: "3.10"
25+
cache: "pip"
26+
- uses: jiro4989/setup-nim-action@v1
27+
with:
28+
nim-version: "1.x"
29+
- run: nimble build -d:release -Y
30+
- run: pip install seleniumbase
31+
- run: seleniumbase install chromedriver
32+
- uses: supercharge/[email protected]
33+
- name: Prepare Nitter
34+
run: |
35+
sudo apt install libsass-dev -y
36+
cp nitter.example.conf nitter.conf
37+
nimble md
38+
nimble scss
39+
- name: Run tests
40+
run: |
41+
./nitter &
42+
pytest -n4 tests

Dockerfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM nimlang/nim:1.6.2-alpine-regular as nim
1+
FROM nimlang/nim:1.6.10-alpine-regular as nim
22
LABEL maintainer="[email protected]"
33

44
RUN apk --no-cache add libsass-dev pcre
@@ -20,4 +20,6 @@ COPY --from=nim /src/nitter/nitter ./
2020
COPY --from=nim /src/nitter/nitter.example.conf ./nitter.conf
2121
COPY --from=nim /src/nitter/public ./public
2222
EXPOSE 8080
23+
RUN adduser -h /src/ -D -s /bin/sh nitter
24+
USER nitter
2325
CMD ./nitter

Dockerfile.arm64

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM alpine:3.17 as nim
2+
LABEL maintainer="[email protected]"
3+
4+
RUN apk --no-cache add gcc git libc-dev libsass-dev "nim=1.6.8-r0" nimble pcre
5+
6+
WORKDIR /src/nitter
7+
8+
COPY nitter.nimble .
9+
RUN nimble install -y --depsOnly
10+
11+
COPY . .
12+
RUN nimble build -d:danger -d:lto -d:strip \
13+
&& nimble scss \
14+
&& nimble md
15+
16+
FROM alpine:3.17
17+
WORKDIR /src/
18+
RUN apk --no-cache add ca-certificates pcre openssl1.1-compat
19+
COPY --from=nim /src/nitter/nitter ./
20+
COPY --from=nim /src/nitter/nitter.example.conf ./nitter.conf
21+
COPY --from=nim /src/nitter/public ./public
22+
EXPOSE 8080
23+
CMD ./nitter

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ performance reasons.
109109

110110
### Docker
111111

112-
#### NOTE: For ARM64/ARM support, please use [unixfox's image](https://quay.io/repository/unixfox/nitter?tab=tags), more info [here](https://github.com/zedeus/nitter/issues/399#issuecomment-997263495)
112+
Page for the Docker image: https://hub.docker.com/r/zedeus/nitter
113+
114+
#### NOTE: For ARM64 support, please use the separate ARM64 docker image: [`zedeus/nitter:latest-arm64`](https://hub.docker.com/r/zedeus/nitter/tags).
113115

114116
To run Nitter with Docker, you'll need to install and run Redis separately
115117
before you can run the container. See below for how to also run Redis using
@@ -122,6 +124,8 @@ docker build -t nitter:latest .
122124
docker run -v $(pwd)/nitter.conf:/src/nitter.conf -d --network host nitter:latest
123125
```
124126

127+
Note: For ARM64, use this Dockerfile: [`Dockerfile.arm64`](https://github.com/zedeus/nitter/blob/master/Dockerfile.arm64).
128+
125129
A prebuilt Docker image is provided as well:
126130

127131
```bash

config.nims

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
--define:ssl
22
--define:useStdLib
3+
--threads:off
34

45
# workaround httpbeast file upload bug
56
--assertions:off

docker-compose.yml

+13-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ services:
88
ports:
99
- "127.0.0.1:8080:8080" # Replace with "8080:8080" if you don't use a reverse proxy
1010
volumes:
11-
- ./nitter.conf:/src/nitter.conf:ro
11+
- ./nitter.conf:/src/nitter.conf:Z,ro
1212
depends_on:
1313
- nitter-redis
1414
restart: unless-stopped
@@ -17,6 +17,12 @@ services:
1717
interval: 30s
1818
timeout: 5s
1919
retries: 2
20+
user: "998:998"
21+
read_only: true
22+
security_opt:
23+
- no-new-privileges:true
24+
cap_drop:
25+
- ALL
2026

2127
nitter-redis:
2228
image: redis:6-alpine
@@ -30,6 +36,12 @@ services:
3036
interval: 30s
3137
timeout: 5s
3238
retries: 2
39+
user: "999:1000"
40+
read_only: true
41+
security_opt:
42+
- no-new-privileges:true
43+
cap_drop:
44+
- ALL
3345

3446
volumes:
3547
nitter-redis:

nitter.example.conf

+1-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ tokenCount = 10
3737
[Preferences]
3838
theme = "Nitter"
3939
replaceTwitter = "nitter.net"
40-
replaceYouTube = "piped.kavin.rocks"
40+
replaceYouTube = "piped.video"
4141
replaceReddit = "teddit.net"
42-
replaceInstagram = ""
4342
proxyVideos = true
4443
hlsPlayback = false
4544
infiniteScroll = false

nitter.nimble

+10-10
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ bin = @["nitter"]
1111
# Dependencies
1212

1313
requires "nim >= 1.4.8"
14-
requires "jester >= 0.5.0"
15-
requires "karax#5498909"
16-
requires "sass#e683aa1"
17-
requires "nimcrypto#a5742a9"
18-
requires "markdown#a661c26"
19-
requires "packedjson#d11d167"
20-
requires "supersnappy#2.1.1"
14+
requires "jester#baca3f"
15+
requires "karax#9ee695b"
16+
requires "sass#7dfdd03"
17+
requires "nimcrypto#4014ef9"
18+
requires "markdown#158efe3"
19+
requires "packedjson#9e6fbb6"
20+
requires "supersnappy#6c94198"
2121
requires "redpool#8b7c1db"
2222
requires "https://github.com/zedeus/redis#d0a0e6f"
23-
requires "zippy#0.9.11"
24-
requires "flatty#0.2.3"
25-
requires "jsony#d0e69bd"
23+
requires "zippy#ca5989a"
24+
requires "flatty#e668085"
25+
requires "jsony#ea811be"
2626

2727

2828
# Tasks

public/js/hls.light.min.js

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/robots.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
User-agent: *
22
Disallow: /
3+
Crawl-delay: 1
34
User-agent: Twitterbot
45
Disallow:

src/api.nim

+24-22
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@ import packedjson
44
import types, query, formatters, consts, apiutils, parser
55
import experimental/parser as newParser
66

7-
proc getGraphUser*(id: string): Future[User] {.async.} =
7+
proc getGraphUser*(username: string): Future[User] {.async.} =
8+
if username.len == 0: return
9+
let
10+
variables = """{
11+
"screen_name": "$1",
12+
"withSafetyModeUserFields": false,
13+
"withSuperFollowsUserFields": false
14+
}""" % [username]
15+
js = await fetchRaw(graphUser ? {"variables": variables}, Api.userScreenName)
16+
result = parseGraphUser(js)
17+
18+
proc getGraphUserById*(id: string): Future[User] {.async.} =
819
if id.len == 0 or id.any(c => not c.isDigit): return
920
let
10-
variables = %*{"userId": id, "withSuperFollowsUserFields": true}
11-
js = await fetchRaw(graphUser ? {"variables": $variables}, Api.userRestId)
21+
variables = """{"userId": "$1", "withSuperFollowsUserFields": true}""" % [id]
22+
js = await fetchRaw(graphUserById ? {"variables": variables}, Api.userRestId)
1223
result = parseGraphUser(js)
1324

1425
proc getGraphListBySlug*(name, list: string): Future[List] {.async.} =
@@ -53,20 +64,6 @@ proc getListTimeline*(id: string; after=""): Future[Timeline] {.async.} =
5364
url = listTimeline ? ps
5465
result = parseTimeline(await fetch(url, Api.timeline), after)
5566

56-
proc getUser*(username: string): Future[User] {.async.} =
57-
if username.len == 0: return
58-
let
59-
ps = genParams({"screen_name": username})
60-
json = await fetchRaw(userShow ? ps, Api.userShow)
61-
result = parseUser(json, username)
62-
63-
proc getUserById*(userId: string): Future[User] {.async.} =
64-
if userId.len == 0: return
65-
let
66-
ps = genParams({"user_id": userId})
67-
json = await fetchRaw(userShow ? ps, Api.userShow)
68-
result = parseUser(json)
69-
7067
proc getTimeline*(id: string; after=""; replies=false): Future[Timeline] {.async.} =
7168
if id.len == 0: return
7269
let
@@ -110,16 +107,21 @@ proc getSearch*[T](query: Query; after=""): Future[Result[T]] {.async.} =
110107
except InternalError:
111108
return Result[T](beginning: true, query: query)
112109

113-
proc getTweetImpl(id: string; after=""): Future[Conversation] {.async.} =
114-
let url = tweet / (id & ".json") ? genParams(cursor=after)
115-
result = parseConversation(await fetch(url, Api.tweet), id)
110+
proc getGraphTweet(id: string; after=""): Future[Conversation] {.async.} =
111+
if id.len == 0: return
112+
let
113+
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
114+
variables = tweetVariables % [id, cursor]
115+
params = {"variables": variables, "features": tweetFeatures}
116+
js = await fetch(graphTweet ? params, Api.tweetDetail)
117+
result = parseGraphConversation(js, id)
116118

117119
proc getReplies*(id, after: string): Future[Result[Chain]] {.async.} =
118-
result = (await getTweetImpl(id, after)).replies
120+
result = (await getGraphTweet(id, after)).replies
119121
result.beginning = after.len == 0
120122

121123
proc getTweet*(id: string; after=""): Future[Conversation] {.async.} =
122-
result = await getTweetImpl(id)
124+
result = await getGraphTweet(id)
123125
if after.len > 0:
124126
result.replies = await getReplies(id, after)
125127

src/apiutils.nim

+14-6
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ proc genParams*(pars: openArray[(string, string)] = @[]; cursor="";
2323
result &= ("count", count)
2424
if cursor.len > 0:
2525
# The raw cursor often has plus signs, which sometimes get turned into spaces,
26-
# so we need to them back into a plus
26+
# so we need to turn them back into a plus
2727
if " " in cursor:
2828
result &= ("cursor", cursor.replace(" ", "+"))
2929
else:
@@ -61,12 +61,20 @@ template fetchImpl(result, fetchBody) {.dirty.} =
6161
try:
6262
var resp: AsyncResponse
6363
pool.use(genHeaders(token)):
64-
resp = await c.get($url)
65-
result = await resp.body
64+
template getContent =
65+
resp = await c.get($url)
66+
result = await resp.body
6667

67-
if resp.status == $Http503:
68-
badClient = true
69-
raise newException(InternalError, result)
68+
getContent()
69+
70+
# Twitter randomly returns 401 errors with an empty body quite often.
71+
# Retrying the request usually works.
72+
if resp.status == "401 Unauthorized" and result.len == 0:
73+
getContent()
74+
75+
if resp.status == $Http503:
76+
badClient = true
77+
raise newException(InternalError, result)
7078

7179
if result.len > 0:
7280
if resp.headers.getOrDefault("content-encoding") == "gzip":

0 commit comments

Comments
 (0)