add my fork changes
This commit is contained in:
parent
cdff5e9b1c
commit
f2443a0da2
7 changed files with 188 additions and 34 deletions
|
@ -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
|
|
@ -1,14 +1,19 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
|
||||
networks:
|
||||
nitter:
|
||||
image: zedeus/nitter:latest
|
||||
|
||||
services:
|
||||
nitter:
|
||||
build: .
|
||||
container_name: nitter
|
||||
hostname: nitter
|
||||
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:
|
||||
- ./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:
|
||||
- nitter-redis
|
||||
restart: unless-stopped
|
||||
|
@ -23,6 +28,8 @@ services:
|
|||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
networks:
|
||||
- nitter
|
||||
|
||||
nitter-redis:
|
||||
image: redis:6-alpine
|
||||
|
@ -42,6 +49,8 @@ services:
|
|||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
networks:
|
||||
- nitter
|
||||
|
||||
volumes:
|
||||
nitter-redis:
|
||||
|
|
|
@ -202,7 +202,7 @@ proc initAccountPool*(cfg: Config; path: string) =
|
|||
quit 1
|
||||
|
||||
let accountsPrePurge = accountPool.len
|
||||
accountPool.keepItIf(not it.hasExpired)
|
||||
#accountPool.keepItIf(not it.hasExpired)
|
||||
|
||||
log "Successfully added ", accountPool.len, " valid accounts."
|
||||
if accountsPrePurge > accountPool.len:
|
||||
|
|
|
@ -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]
|
||||
twitter_api, unsupported, embed, resolver, router_utils]
|
||||
|
||||
const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
|
||||
const issuesUrl = "https://github.com/zedeus/nitter/issues"
|
||||
|
@ -53,6 +53,7 @@ createSearchRouter(cfg)
|
|||
createMediaRouter(cfg)
|
||||
createEmbedRouter(cfg)
|
||||
createRssRouter(cfg)
|
||||
createTwitterApiRouter(cfg)
|
||||
createDebugRouter(cfg)
|
||||
|
||||
settings:
|
||||
|
|
171
src/routes/twitter_api.nim
Normal file
171
src/routes/twitter_api.nim
Normal 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
|
|
@ -32,8 +32,6 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
|
|||
if cfg.enableRss and rss.len > 0:
|
||||
icon "rss-feed", title="RSS Feed", href=rss
|
||||
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))
|
||||
|
||||
proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||
|
|
|
@ -13,7 +13,7 @@ proc renderStat(num: int; class: string; text=""): VNode =
|
|||
text insertSep($num, ',')
|
||||
|
||||
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"):
|
||||
let
|
||||
url = getPicUrl(user.getUserPic())
|
||||
|
|
Loading…
Reference in a new issue