add my fork changes

This commit is contained in:
taskylizard 2024-02-24 13:36:22 +00:00
parent cdff5e9b1c
commit f2443a0da2
No known key found for this signature in database
GPG key ID: 1820131ED1A24120
7 changed files with 188 additions and 34 deletions

View file

@ -1,25 +0,0 @@
FROM alpine:3.18 as nim
LABEL maintainer="setenforce@protonmail.com"
RUN apk --no-cache add libsass-dev pcre gcc git libc-dev "nim=1.6.14-r0" "nimble=0.13.1-r2"
WORKDIR /src/nitter
COPY nitter.nimble .
RUN nimble install -y --depsOnly
COPY . .
RUN nimble build -d:danger -d:lto -d:strip \
&& nimble scss \
&& nimble md
FROM alpine:3.18
WORKDIR /src/
RUN apk --no-cache add pcre ca-certificates openssl1.1-compat
COPY --from=nim /src/nitter/nitter ./
COPY --from=nim /src/nitter/nitter.example.conf ./nitter.conf
COPY --from=nim /src/nitter/public ./public
EXPOSE 8080
RUN adduser -h /src/ -D -s /bin/sh nitter
USER nitter
CMD ./nitter

View file

@ -1,14 +1,19 @@
version: "3" version: "3"
services: networks:
nitter: nitter:
image: zedeus/nitter:latest
services:
nitter:
build: .
container_name: nitter container_name: nitter
hostname: nitter
ports: ports:
- "127.0.0.1:8080:8080" # Replace with "8080:8080" if you don't use a reverse proxy - "8002:8080" # Replace with "8080:8080" if you don't use a reverse proxy
volumes: volumes:
- ./nitter.conf:/src/nitter.conf:Z,ro - ./nitter.conf:/src/nitter.conf:Z,ro
- ./guest_accounts.json:/src/guest_accounts.json:Z,ro
- ./public/.twitterminator.txt:/src/public/.twitterminator.txt:Z,ro
depends_on: depends_on:
- nitter-redis - nitter-redis
restart: unless-stopped restart: unless-stopped
@ -23,6 +28,8 @@ services:
- no-new-privileges:true - no-new-privileges:true
cap_drop: cap_drop:
- ALL - ALL
networks:
- nitter
nitter-redis: nitter-redis:
image: redis:6-alpine image: redis:6-alpine
@ -42,6 +49,8 @@ services:
- no-new-privileges:true - no-new-privileges:true
cap_drop: cap_drop:
- ALL - ALL
networks:
- nitter
volumes: volumes:
nitter-redis: nitter-redis:

View file

@ -202,7 +202,7 @@ proc initAccountPool*(cfg: Config; path: string) =
quit 1 quit 1
let accountsPrePurge = accountPool.len let accountsPrePurge = accountPool.len
accountPool.keepItIf(not it.hasExpired) #accountPool.keepItIf(not it.hasExpired)
log "Successfully added ", accountPool.len, " valid accounts." log "Successfully added ", accountPool.len, " valid accounts."
if accountsPrePurge > accountPool.len: if accountsPrePurge > accountPool.len:

View file

@ -10,7 +10,7 @@ import types, config, prefs, formatters, redis_cache, http_pool, auth
import views/[general, about] import views/[general, about]
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] twitter_api, unsupported, embed, resolver, router_utils]
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances" const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
const issuesUrl = "https://github.com/zedeus/nitter/issues" const issuesUrl = "https://github.com/zedeus/nitter/issues"
@ -53,6 +53,7 @@ createSearchRouter(cfg)
createMediaRouter(cfg) createMediaRouter(cfg)
createEmbedRouter(cfg) createEmbedRouter(cfg)
createRssRouter(cfg) createRssRouter(cfg)
createTwitterApiRouter(cfg)
createDebugRouter(cfg) createDebugRouter(cfg)
settings: settings:

171
src/routes/twitter_api.nim Normal file
View file

@ -0,0 +1,171 @@
# SPDX-License-Identifier: AGPL-3.0-only
import json, asyncdispatch, options, uri
import times
import jester
import router_utils
import ".."/[types, api, apiutils, query, consts]
import httpclient, strutils
import sequtils
export api
proc videoToJson*(t: Video): JsonNode =
result = newJObject()
result["durationMs"] = %t.durationMs
result["url"] = %t.url
result["thumb"] = %t.thumb
result["views"] = %t.views
result["available"] = %t.available
result["reason"] = %t.reason
result["title"] = %t.title
result["description"] = %t.description
# result["playbackType"] = %t.playbackType
# result["variants"] = %t.variants
# playbackType*: VideoType
# variants*: seq[VideoVariant]
proc tweetToJson*(t: Tweet): JsonNode =
result = newJObject()
result["id"] = %t.id
result["threadId"] = %t.threadId
result["replyId"] = %t.replyId
result["user"] = %*{ "username": t.user.username }
result["text"] = %t.text
result["time"] = newJString(times.format(t.time, "yyyy-MM-dd'T'HH:mm:ss"))
result["reply"] = %t.reply
result["pinned"] = %t.pinned
result["hasThread"] = %t.hasThread
result["available"] = %t.available
result["tombstone"] = %t.tombstone
result["location"] = %t.location
result["source"] = %t.source
# result["stats"] = toJson(t.stats) # Define conversion for TweetStats type
# result["retweet"] = t.retweet.map(toJson) # Define conversion for Tweet type
# result["attribution"] = t.attribution.map(toJson) # Define conversion for User type
# result["mediaTags"] = toJson(t.mediaTags) # Define conversion for seq[User]
# result["quote"] = t.quote.map(toJson) # Define conversion for Tweet type
# result["card"] = t.card.map(toJson) # Define conversion for Card type
# result["poll"] = t.poll.map(toJson) # Define conversion for Poll type
# result["gif"] = t.gif.map(toJson) # Define conversion for Gif type
# result["video"] = videoToJson(t.video.get())
result["photos"] = %t.photos
proc getUserProfileJson*(username: string): Future[JsonNode] {.async.} =
let user: User = await getGraphUser(username)
let response: JsonNode = %*{
"id": user.id,
"username": user.username
}
result = response
proc getUserTweetsJson*(id: string): Future[JsonNode] {.async.} =
let tweetsGraph = await getGraphUserTweets(id, TimelineKind.tweets)
let repliesGraph = await getGraphUserTweets(id, TimelineKind.replies)
let mediaGraph = await getGraphUserTweets(id, TimelineKind.media)
let tweetsContent = tweetsGraph.tweets.content[0]
let tweetsJson = tweetsContent.map(tweetToJson)
let repliesContent = repliesGraph.tweets.content[0]
let repliesJson = repliesContent.map(tweetToJson)
let mediaContent = mediaGraph.tweets.content[0]
let mediaJson = mediaContent.map(tweetToJson)
let response: JsonNode = %*{
"tweets": %tweetsJson,
"replies": %repliesJson,
"media": %mediaJson
}
result = response
proc searchTimeline*(query: Query; after=""): Future[string] {.async.} =
let q = genQueryParam(query)
var
variables = %*{
"rawQuery": q,
"count": 20,
"product": "Latest",
"withDownvotePerspective": false,
"withReactionsMetadata": false,
"withReactionsPerspective": false
}
if after.len > 0:
variables["cursor"] = % after
let url = graphSearchTimeline ? {"variables": $variables, "features": gqlFeatures}
result = await fetchRaw(url, Api.search)
proc getUserTweets*(id: string; after=""): Future[string] {.async.} =
if id.len == 0: return
let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
variables = userTweetsVariables % [id, cursor]
params = {"variables": variables, "features": gqlFeatures}
result = await fetchRaw(graphUserTweets ? params, Api.userTweets)
proc getUserReplies*(id: string; after=""): Future[string] {.async.} =
if id.len == 0: return
let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
variables = userTweetsVariables % [id, cursor]
params = {"variables": variables, "features": gqlFeatures}
result = await fetchRaw(graphUserTweets ? params, Api.userTweetsAndReplies)
proc getUserMedia*(id: string; after=""): Future[string] {.async.} =
if id.len == 0: return
let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
variables = userTweetsVariables % [id, cursor]
params = {"variables": variables, "features": gqlFeatures}
result = await fetchRaw(graphUserTweets ? params, Api.userMedia)
proc getTweetById*(id: string; after=""): Future[string] {.async.} =
let
cursor = if after.len > 0: "\"cursor\":\"$1\"," % after else: ""
variables = tweetVariables % [id, cursor]
params = {"variables": variables, "features": gqlFeatures}
result = await fetchRaw(graphTweet ? params, Api.tweetDetail)
proc createTwitterApiRouter*(cfg: Config) =
router api:
get "/api/echo":
resp Http200, {"Content-Type": "text/html"}, "hello, world!"
get "/api/user/@username":
let username = @"username"
let response = await getUserProfileJson(username)
respJson response
# get "/api/user/@id/tweets":
# let id = @"id"
# let response = await getUserTweetsJson(id)
# respJson response
get "/api/user/@username/timeline":
let username = @"username"
let query = Query(fromUser: @[username])
let response = await searchTimeline(query)
resp Http200, { "Content-Type": "application/json" }, response
get "/api/user/@id/tweets":
let id = @"id"
let after = getCursor()
let response = await getUserTweets(id, after)
resp Http200, { "Content-Type": "application/json" }, response
get "/api/user/@id/replies":
let id = @"id"
let response = await getUserReplies(id)
resp Http200, { "Content-Type": "application/json" }, response
get "/api/user/@id/media":
let id = @"id"
let response = await getUserMedia(id)
resp Http200, { "Content-Type": "application/json" }, response
get "/api/tweet/@id":
let id = @"id"
let response = await getTweetById(id)
resp Http200, { "Content-Type": "application/json" }, response

View file

@ -32,8 +32,6 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
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 icon "bird", title="Open in Twitter", href=canonical
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="";

View file

@ -13,7 +13,7 @@ proc renderStat(num: int; class: string; text=""): VNode =
text insertSep($num, ',') text insertSep($num, ',')
proc renderUserCard*(user: User; prefs: Prefs): VNode = proc renderUserCard*(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="profile-card")): buildHtml(tdiv(class="profile-card", "data-profile-id" = $user.id)):
tdiv(class="profile-card-info"): tdiv(class="profile-card-info"):
let let
url = getPicUrl(user.getUserPic()) url = getPicUrl(user.getUserPic())