Skip to content

Commit

Permalink
merge squashed feature-notes zedeus#653
Browse files Browse the repository at this point in the history
Signed-off-by: r3g_5z <[email protected]>
  • Loading branch information
girlbossceo committed Jan 21, 2023
1 parent 075ab51 commit db207df
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 10 deletions.
6 changes: 6 additions & 0 deletions src/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ proc getGraphListMembers*(list: List; after=""): Future[Result[User]] {.async.}
let url = graphListMembers ? {"variables": $variables}
result = parseGraphListMembers(await fetchRaw(url, Api.listMembers), after)

proc getGraphArticle*(id: string): Future[Article] {.async.} =
let
variables = %*{"twitterArticleId": id}
url = graphArticle ? {"variables": $variables}
result = parseGraphArticle(await fetch(url, Api.userRestId))

proc getListTimeline*(id: string; after=""): Future[Timeline] {.async.} =
if id.len == 0: return
let
Expand Down
1 change: 1 addition & 0 deletions src/consts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const
graphList* = graphql / "JADTh6cjebfgetzvF3tQvQ/List"
graphListBySlug* = graphql / "ErWsz9cObLel1BF-HjuBlA/ListBySlug"
graphListMembers* = graphql / "Ke6urWMeCV2UlKXGRy4sow/ListMembers"
graphArticle* = graphql / "rJMGbcr9LTsjVycjUmcnEg/TwitterArticleByRestId"

timelineParams* = {
"include_profile_interstitial_type": "0",
Expand Down
19 changes: 14 additions & 5 deletions src/formatters.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ let
userPicRegex = re"_(normal|bigger|mini|200x200|400x400)(\.[A-z]+)$"
extRegex = re"(\.[A-z]+)$"
illegalXmlRegex = re"(*UTF8)[^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]"
hashtagRegex = re"\B#(\w*[A-Za-z]\w*)\b"
mentionRegex = re"\B@(\w{1,15})\b"

proc getUrlPrefix*(cfg: Config): string =
if cfg.useHttps: https & cfg.hostname
Expand Down Expand Up @@ -76,6 +78,13 @@ proc replaceUrls*(body: string; prefs: Prefs; absolute=""): string =
if absolute.len > 0 and "href" in result:
result = result.replace("href=\"/", &"href=\"{absolute}/")

proc replaceHashtagsAndMentions*(body: string): string =
result = body
result = result.replacef(hashtagRegex, a(
"#$1", href = "/search?q=%23$1"))
result = result.replacef(mentionRegex, a(
"@$1", href = "/$1"))

proc getM3u8Url*(content: string): string =
var matches: array[1, string]
if re.find(content, m3u8Regex, matches) != -1:
Expand Down Expand Up @@ -125,14 +134,14 @@ proc getTime*(tweet: Tweet): string =
proc getRfc822Time*(tweet: Tweet): string =
tweet.time.format("ddd', 'dd MMM yyyy HH:mm:ss 'GMT'")

proc getShortTime*(tweet: Tweet): string =
proc getShortTime*(time: DateTime): string =
let now = now()
let since = now - tweet.time
let since = now - time

if now.year != tweet.time.year:
result = tweet.time.format("d MMM yyyy")
if now.year != time.year:
result = time.format("d MMM yyyy")
elif since.inDays >= 1:
result = tweet.time.format("MMM d")
result = time.format("MMM d")
elif since.inHours >= 1:
result = $since.inHours & "h"
elif since.inMinutes >= 1:
Expand Down
4 changes: 3 additions & 1 deletion src/nitter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import types, config, prefs, formatters, redis_cache, http_pool, tokens
import views/[general, about]
import routes/[
preferences, timeline, status, media, search, rss, list, debug,
unsupported, embed, resolver, router_utils]
unsupported, embed, notes, resolver, router_utils]

const instancesUrl = "https://github.com/zedeus/nitter/wiki/Instances"
const issuesUrl = "https://github.com/zedeus/nitter/issues"
Expand Down Expand Up @@ -48,6 +48,7 @@ createListRouter(cfg)
createStatusRouter(cfg)
createSearchRouter(cfg)
createMediaRouter(cfg)
createNotesRouter(cfg)
createEmbedRouter(cfg)
createRssRouter(cfg)
createDebugRouter(cfg)
Expand Down Expand Up @@ -100,4 +101,5 @@ routes:
extend status, ""
extend media, ""
extend embed, ""
extend notes, ""
extend debug, ""
68 changes: 68 additions & 0 deletions src/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,71 @@ proc parsePhotoRail*(js: JsonNode): PhotoRail =

if url.len == 0: continue
result.add GalleryPhoto(url: url, tweetId: $t.id)

proc parseGraphArticle*(js: JsonNode): Article =
if not js{"errors"}.isNull:
return

let article = js{"data", "twitterArticle"}
let meta = article{"metadata"}

result = Article(
title: article{"title"}.getStr,
coverImage: article{"cover_image", "media_info", "original_img_url"}.getStr,
user: meta{"authorResults", "result", "legacy"}.parseUser,
time: meta{"publishedAtMs"}.getStr.parseInt.div(1000).fromUnix.utc,
)

let
content = article{"data", "contentStateJson"}.getStr.parseJson

for p in content{"blocks"}:
var paragraph = ArticleParagraph(
text: p{"text"}.getStr,
baseType: parseEnum[ArticleType](p{"type"}.getStr)
)
for sr in p{"inlineStyleRanges"}:
paragraph.inlineStyleRanges.add ArticleStyleRange(
offset: sr{"offset"}.getInt,
length: sr{"length"}.getInt,
style: parseEnum[ArticleStyle](sr{"style"}.getStr)
)
for er in p{"entityRanges"}:
paragraph.entityRanges.add ArticleEntityRange(
offset: er{"offset"}.getInt,
length: er{"length"}.getInt,
key: er{"key"}.getInt
)
result.paragraphs.add paragraph

# Note: This is a map but the indices are integers so it's fine.
for _, jEntity in content{"entityMap"}:
var entity = ArticleEntity(
entityType: parseEnum[ArticleEntityType] jEntity{"type"}.getStr,
)
case entity.entityType
of ArticleEntityType.link:
entity.url = jEntity{"data", "url"}.getStr
of ArticleEntityType.media:
for jMedia in jEntity{"data", "mediaItems"}:
entity.mediaIds.add jMedia{"mediaId"}.getStr
of ArticleEntityType.tweet:
entity.tweetId = jEntity{"data", "tweetId"}.getStr
of ArticleEntityType.twemoji:
entity.twemoji = jEntity{"data", "url"}.getStr
else: discard

result.entities.add entity

for m in article{"media"}:
let mediaInfo = m{"media_info"}
var media = ArticleMedia(
mediaType: parseEnum[ArticleMediaType](mediaInfo{"__typename"}.getStr)
)
case media.mediaType
of ArticleMediaType.image:
media.url = mediaInfo{"original_img_url"}.getStr
of ArticleMediaType.gif:
media.url = mediaInfo{"variants"}[0]{"url"}.getStr
else: discard
result.media[m{"media_id"}.getStr] = media
34 changes: 34 additions & 0 deletions src/routes/notes.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: AGPL-3.0-only
import asyncdispatch, tables, asyncfutures, options
import jester, karax/vdom
import ".."/[types, api]
import ../views/[notes, tweet, general]
import router_utils

export api, notes, vdom, tweet, general, router_utils

proc createNotesRouter*(cfg: Config) =
router notes:
get "/i/notes/@id":
let article = await getGraphArticle(@"id")

if article == nil:
resp Http404

var tweetFutures: seq[Future[Conversation]]
for e in article.entities:
if e.entityType == ArticleEntityType.tweet:
tweetFutures.add getTweet(e.tweetId)

let convs = await tweetFutures.all

var tweets = initTable[int64, Tweet]()
for c in convs:
if c != nil and c.tweet != nil:
tweets[c.tweet.id] = c.tweet

let
path = getPath()
prefs = cookiePrefs()
note = renderNote(article, tweets, path, prefs)
resp renderMain(note, request, cfg, prefs, titleText=article.title)
2 changes: 1 addition & 1 deletion src/routes/unsupported.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ proc createUnsupportedRouter*(cfg: Config) =
feature()

get "/i/@i?/?@j?":
cond @"i" notin ["status", "lists" , "user"]
cond @"i" notin ["status", "lists" , "user", "notes"]
feature()
1 change: 1 addition & 0 deletions src/sass/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@import 'inputs';
@import 'timeline';
@import 'search';
@import 'note';

body {
// colors
Expand Down
60 changes: 60 additions & 0 deletions src/sass/note.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.note {
width: 600px;
margin: 0 auto;
background-color: var(--bg_panel);
font-family: sans-serif;

img.cover {
margin: 0;
width: 100%;
}

article {
padding: 20px;

&>h1 {
display: inherit;
font-size: 2.5rem;
margin: 30px 0;
}

&>p,
li {
font-size: 18px;
}

&>p {
line-height: 1.5em;
margin: 30px 0;
word-wrap: break-word;
white-space: break-spaces;
}

&>span.image {
text-align: center;
width: 100%;

img,
video {
max-width: 100%;
border-radius: 20px;
margin: 0 auto;
}
}

img.twemoji {
width: 18px;
height: 18px;
}

&>ul>li,
&>ol>li {
line-height: 2em;
}

&>iframe {
width: 100%;
height: 400px;
}
}
}
65 changes: 65 additions & 0 deletions src/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type
protectedUser = 22
couldntAuth = 32
doesntExist = 34
invalidPermission = 37
userNotFound = 50
suspended = 63
rateLimited = 88
Expand Down Expand Up @@ -116,6 +117,70 @@ type

PhotoRail* = seq[GalleryPhoto]

Article* = ref object
title*: string
coverImage*: string
user*: User
time*: DateTime
paragraphs*: seq[ArticleParagraph]
entities*: seq[ArticleEntity]
media*: Table[string, ArticleMedia]

ArticleParagraph* = object
text*: string
baseType*: ArticleType
inlineStyleRanges*: seq[ArticleStyleRange]
entityRanges*: seq[ArticleEntityRange]

ArticleType* {.pure.} = enum
headerOne = "header-one"
headerTwo = "header-two"
headerThree = "header-three"
orderedListItem = "ordered-list-item"
unorderedListItem = "unordered-list-item"
unstyled = "unstyled"
atomic = "atomic"
unknown

ArticleStyleRange* = object
offset*: int
length*: int
style*: ArticleStyle

ArticleStyle* {.pure.} = enum
bold = "BOLD"
italic = "ITALIC"
strikethrough = "STRIKETHROUGH"
unknown

ArticleEntityRange* = object
offset*: int
length*: int
key*: int

ArticleEntity* = object
entityType*: ArticleEntityType
url*: string
mediaIds*: seq[string]
tweetId*: string
twemoji*: string

ArticleEntityType* {.pure.} = enum
link = "LINK"
media = "MEDIA"
tweet = "TWEET"
twemoji = "TWEMOJI"
unknown

ArticleMedia* = object
mediaType*: ArticleMediaType
url*: string

ArticleMediaType* {.pure.} = enum
image = "ApiImage"
gif = "ApiGif"
unknown

Poll* = object
options*: seq[string]
values*: seq[int]
Expand Down
1 change: 1 addition & 0 deletions src/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const
"pic.twitter.com",
"twimg.com",
"abs.twimg.com",
"abs-0.twimg.com",
"pbs.twimg.com",
"video.twimg.com"
]
Expand Down
2 changes: 1 addition & 1 deletion src/views/general.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"

buildHtml(head):
link(rel="stylesheet", type="text/css", href="/css/style.css?v=18")
link(rel="stylesheet", type="text/css", href="/css/style.css?v=19")
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=2")

if theme.len > 0:
Expand Down
Loading

0 comments on commit db207df

Please sign in to comment.