Merge remote-tracking branch 'upstream/master'
I really hope nothing breaks! :3
This commit is contained in:
@@ -6,7 +6,7 @@ import types, utils, query
|
||||
const
|
||||
cards = "cards.twitter.com/cards"
|
||||
tco = "https://t.co"
|
||||
twitter = parseUri("https://twitter.com")
|
||||
twitter = parseUri("https://x.com")
|
||||
|
||||
let
|
||||
twRegex = re"(?<=(?<!\S)https:\/\/|(?<=\s))(www\.|mobile\.)?twitter\.com"
|
||||
@@ -64,18 +64,20 @@ proc replaceUrls*(body: string; prefs: Prefs; absolute=""): string =
|
||||
result = body
|
||||
|
||||
if prefs.replaceYouTube.len > 0 and "youtu" in result:
|
||||
result = result.replace(ytRegex, prefs.replaceYouTube)
|
||||
let youtubeHost = strip(prefs.replaceYouTube, chars={'/'})
|
||||
result = result.replace(ytRegex, youtubeHost)
|
||||
|
||||
if prefs.replaceTwitter.len > 0:
|
||||
let twitterHost = strip(prefs.replaceTwitter, chars={'/'})
|
||||
if tco in result:
|
||||
result = result.replace(tco, https & prefs.replaceTwitter & "/t.co")
|
||||
result = result.replace(tco, https & twitterHost & "/t.co")
|
||||
if "x.com" in result:
|
||||
result = result.replace(xRegex, prefs.replaceTwitter)
|
||||
result = result.replace(xRegex, twitterHost)
|
||||
result = result.replacef(xLinkRegex, a(
|
||||
prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1"))
|
||||
twitterHost & "$2", href = https & twitterHost & "$1"))
|
||||
if "twitter.com" in result:
|
||||
result = result.replace(cards, prefs.replaceTwitter & "/cards")
|
||||
result = result.replace(twRegex, prefs.replaceTwitter)
|
||||
result = result.replace(cards, twitterHost & "/cards")
|
||||
result = result.replace(twRegex, twitterHost)
|
||||
result = result.replacef(twLinkRegex, a(
|
||||
prefs.replaceTwitter & "$2", href = https & prefs.replaceTwitter & "$1"))
|
||||
if "imgur.com" in result:
|
||||
@@ -96,9 +98,10 @@ proc replaceUrls*(body: string; prefs: Prefs; absolute=""): string =
|
||||
prefs.replaceSoundCloud & "$4", href = https & prefs.replaceSoundCloud & "$2"))
|
||||
|
||||
if prefs.replaceReddit.len > 0 and ("reddit.com" in result or "redd.it" in result):
|
||||
result = result.replace(rdShortRegex, prefs.replaceReddit & "/comments/")
|
||||
result = result.replace(rdRegex, prefs.replaceReddit)
|
||||
if prefs.replaceReddit in result and "/gallery/" in result:
|
||||
let redditHost = strip(prefs.replaceReddit, chars={'/'})
|
||||
result = result.replace(rdShortRegex, redditHost & "/comments/")
|
||||
result = result.replace(rdRegex, redditHost)
|
||||
if redditHost in result and "/gallery/" in result:
|
||||
result = result.replace("/gallery/", "/comments/")
|
||||
|
||||
if absolute.len > 0 and "href" in result:
|
||||
|
||||
@@ -487,6 +487,7 @@ proc parseGraphConversation*(js: JsonNode; tweetId: string): Conversation =
|
||||
result.before.content.add tweet
|
||||
elif entryId.startsWith("cursor-bottom"):
|
||||
var cursorValue = select(
|
||||
e{"content", "value"},
|
||||
e{"content", "content", "value"},
|
||||
e{"content", "itemContent", "value"}
|
||||
)
|
||||
|
||||
@@ -6,10 +6,9 @@ import types
|
||||
const
|
||||
validFilters* = @[
|
||||
"media", "images", "twimg", "videos",
|
||||
"native_video", "consumer_video", "pro_video",
|
||||
"native_video", "consumer_video", "spaces",
|
||||
"links", "news", "quote", "mentions",
|
||||
"replies", "retweets", "nativeretweets",
|
||||
"verified", "safe"
|
||||
"replies", "retweets", "nativeretweets"
|
||||
]
|
||||
|
||||
emptyQuery* = "include:nativeretweets"
|
||||
@@ -18,6 +17,11 @@ template `@`(param: string): untyped =
|
||||
if param in pms: pms[param]
|
||||
else: ""
|
||||
|
||||
proc validateNumber(value: string): string =
|
||||
if value.anyIt(not it.isDigit):
|
||||
return ""
|
||||
return value
|
||||
|
||||
proc initQuery*(pms: Table[string, string]; name=""): Query =
|
||||
result = Query(
|
||||
kind: parseEnum[QueryKind](@"f", tweets),
|
||||
@@ -26,7 +30,7 @@ proc initQuery*(pms: Table[string, string]; name=""): Query =
|
||||
excludes: validFilters.filterIt("e-" & it in pms),
|
||||
since: @"since",
|
||||
until: @"until",
|
||||
near: @"near"
|
||||
minLikes: validateNumber(@"min_faves")
|
||||
)
|
||||
|
||||
if name.len > 0:
|
||||
@@ -81,8 +85,8 @@ proc genQueryParam*(query: Query): string =
|
||||
result &= " since:" & query.since
|
||||
if query.until.len > 0:
|
||||
result &= " until:" & query.until
|
||||
if query.near.len > 0:
|
||||
result &= &" near:\"{query.near}\" within:15mi"
|
||||
if query.minLikes.len > 0:
|
||||
result &= " min_faves:" & query.minLikes
|
||||
if query.text.len > 0:
|
||||
if result.len > 0:
|
||||
result &= " " & query.text
|
||||
@@ -106,8 +110,8 @@ proc genQueryUrl*(query: Query): string =
|
||||
params.add "since=" & query.since
|
||||
if query.until.len > 0:
|
||||
params.add "until=" & query.until
|
||||
if query.near.len > 0:
|
||||
params.add "near=" & query.near
|
||||
if query.minLikes.len > 0:
|
||||
params.add "min_faves=" & query.minLikes
|
||||
|
||||
if params.len > 0:
|
||||
result &= params.join("&")
|
||||
|
||||
@@ -31,8 +31,6 @@ proc createStatusRouter*(cfg: Config) =
|
||||
resp $renderReplies(replies, prefs, getPath())
|
||||
|
||||
let conv = await getTweet(id, getCursor())
|
||||
if conv == nil:
|
||||
warn "nil conv"
|
||||
|
||||
if conv == nil or conv.tweet == nil or conv.tweet.id == 0:
|
||||
var error = "Tweet not found"
|
||||
@@ -68,7 +66,7 @@ proc createStatusRouter*(cfg: Config) =
|
||||
|
||||
get "/@name/@s/@id/@m/?@i?":
|
||||
cond @"s" in ["status", "statuses"]
|
||||
cond @"m" in ["video", "photo"]
|
||||
cond @"m" in ["video", "photo", "history"]
|
||||
redirect("/$1/status/$2" % [@"name", @"id"])
|
||||
|
||||
get "/@name/statuses/@id/?":
|
||||
@@ -76,6 +74,6 @@ proc createStatusRouter*(cfg: Config) =
|
||||
|
||||
get "/i/web/status/@id":
|
||||
redirect("/i/status/" & @"id")
|
||||
|
||||
|
||||
get "/@name/thread/@id/?":
|
||||
redirect("/$1/status/$2" % [@"name", @"id"])
|
||||
|
||||
@@ -105,6 +105,12 @@ proc createTimelineRouter*(cfg: Config) =
|
||||
get "/intent/user":
|
||||
respUserId()
|
||||
|
||||
get "/intent/follow/?":
|
||||
let username = request.params.getOrDefault("screen_name")
|
||||
if username.len == 0:
|
||||
resp Http400, showError("Missing screen_name parameter", cfg)
|
||||
redirect("/" & username)
|
||||
|
||||
get "/@name/?@tab?/?":
|
||||
cond '.' notin @"name"
|
||||
cond @"name" notin ["pic", "gif", "video", "search", "settings", "login", "intent", "i"]
|
||||
|
||||
@@ -17,7 +17,7 @@ proc createUnsupportedRouter*(cfg: Config) =
|
||||
get "/@name/lists/?": feature()
|
||||
|
||||
get "/intent/?@i?":
|
||||
cond @"i" notin ["user"]
|
||||
cond @"i" notin ["user", "follow"]
|
||||
feature()
|
||||
|
||||
get "/i/@i?/?@j?":
|
||||
|
||||
@@ -52,7 +52,7 @@ body {
|
||||
background-color: var(--bg_color);
|
||||
color: var(--fg_color);
|
||||
font-family: $font_stack;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ button {
|
||||
|
||||
input[type="text"],
|
||||
input[type="date"],
|
||||
input[type="number"],
|
||||
select {
|
||||
@include input-colors;
|
||||
background-color: var(--bg_elements);
|
||||
@@ -24,7 +25,12 @@ select {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@@ -38,6 +44,17 @@ input[type="date"]::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
display: none;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="date"]::-webkit-clear-button {
|
||||
margin-left: 17px;
|
||||
filter: grayscale(100%);
|
||||
@@ -176,7 +193,8 @@ input::-webkit-datetime-edit-year-field:focus {
|
||||
color: var(--fg_color);
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
max-width: 140px;
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
height: calc(100% - 4px);
|
||||
width: calc(100% - 8px);
|
||||
}
|
||||
@@ -155,15 +156,14 @@
|
||||
|
||||
.profile-tabs {
|
||||
@include search-resize(820px, 5);
|
||||
@include search-resize(725px, 4);
|
||||
@include search-resize(600px, 6);
|
||||
@include search-resize(560px, 5);
|
||||
@include search-resize(480px, 4);
|
||||
@include search-resize(715px, 4);
|
||||
@include search-resize(700px, 5);
|
||||
@include search-resize(485px, 4);
|
||||
@include search-resize(410px, 3);
|
||||
}
|
||||
|
||||
@include search-resize(560px, 5);
|
||||
@include search-resize(480px, 4);
|
||||
@include search-resize(700px, 5);
|
||||
@include search-resize(485px, 4);
|
||||
@include search-resize(410px, 3);
|
||||
|
||||
@media(max-width: 600px) {
|
||||
|
||||
@@ -141,7 +141,7 @@ type
|
||||
fromUser*: seq[string]
|
||||
since*: string
|
||||
until*: string
|
||||
near*: string
|
||||
minLikes*: string
|
||||
sep*: string
|
||||
|
||||
Gif* = object
|
||||
|
||||
@@ -33,14 +33,14 @@ proc renderNavbar(cfg: Config; req: Request; rss, canonical: string): VNode =
|
||||
tdiv(class="nav-item right"):
|
||||
if cfg.enableRss and rss.len > 0:
|
||||
icon "rss", title="RSS Feed", href=rss
|
||||
icon "bird", title="Open in Twitter", href=canonical
|
||||
icon "bird", title="Open in X", href=canonical
|
||||
a(href="https://buymeacoffee.com/kuu7o"): 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="";
|
||||
video=""; images: seq[string] = @[]; banner=""; ogTitle="";
|
||||
rss=""; canonical=""): VNode =
|
||||
rss=""; alternate=""): VNode =
|
||||
var theme = prefs.theme.toTheme
|
||||
if "theme" in req.params:
|
||||
theme = req.params["theme"].toTheme
|
||||
@@ -54,7 +54,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=20")
|
||||
link(rel="stylesheet", type="text/css", href="/css/style.css?v=21")
|
||||
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=3")
|
||||
|
||||
if theme.len > 0:
|
||||
@@ -68,8 +68,8 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
|
||||
link(rel="search", type="application/opensearchdescription+xml", title=cfg.title,
|
||||
href=opensearchUrl)
|
||||
|
||||
if canonical.len > 0:
|
||||
link(rel="canonical", href=canonical)
|
||||
if alternate.len > 0:
|
||||
link(rel="alternate", href=alternate, title="View on X")
|
||||
|
||||
if cfg.enableRss and rss.len > 0:
|
||||
link(rel="alternate", type="application/rss+xml", href=rss, title="RSS feed")
|
||||
@@ -131,16 +131,16 @@ proc renderMain*(body: VNode; req: Request; cfg: Config; prefs=defaultPrefs;
|
||||
titleText=""; desc=""; ogTitle=""; rss=""; video="";
|
||||
images: seq[string] = @[]; banner=""): string =
|
||||
|
||||
let canonical = getTwitterLink(req.path, req.params)
|
||||
let twitterLink = getTwitterLink(req.path, req.params)
|
||||
|
||||
let node = buildHtml(html(lang="en")):
|
||||
renderHead(prefs, cfg, req, titleText, desc, video, images, banner, ogTitle,
|
||||
rss, canonical)
|
||||
rss, twitterLink)
|
||||
|
||||
body(class=if prefs.verifiedBadge == "Hide all": "hide-verified-all"
|
||||
elif prefs.verifiedBadge == "Show official only": "hide-verified-blue"
|
||||
else: ""):
|
||||
renderNavbar(cfg, req, rss, canonical)
|
||||
renderNavbar(cfg, req, rss, twitterLink)
|
||||
|
||||
tdiv(class="container"):
|
||||
body
|
||||
|
||||
@@ -89,6 +89,13 @@ proc genDate*(pref, state: string): VNode =
|
||||
input(name=pref, `type`="date", value=state)
|
||||
icon "calendar"
|
||||
|
||||
proc genNumberInput*(pref, label, state, placeholder: string; class=""; autofocus=true; min="0"): VNode =
|
||||
let p = placeholder
|
||||
buildHtml(tdiv(class=("pref-group pref-input " & class))):
|
||||
if label.len > 0:
|
||||
label(`for`=pref): text label
|
||||
input(name=pref, `type`="number", placeholder=p, value=state, autofocus=(autofocus and state.len == 0), min=min, step="1")
|
||||
|
||||
proc genImg*(url: string; class=""): VNode =
|
||||
buildHtml():
|
||||
img(src=getPicUrl(url), class=class, alt="", loading="lazy")
|
||||
|
||||
@@ -57,7 +57,7 @@ proc renderThread(thread: Tweets; prefs: Prefs; path: string): VNode =
|
||||
|
||||
proc renderUser*(user: User; prefs: Prefs; path: string): VNode =
|
||||
let class = if user.sensitive: "timeline-item nsfw" else: "timeline-item"
|
||||
buildHtml(tdiv(class=class)):
|
||||
buildHtml(tdiv(class=class, data-username=user.username)):
|
||||
a(class="tweet-link", href=("/" & user.username))
|
||||
tdiv(class="tweet-body profile-result"):
|
||||
tdiv(class="tweet-header"):
|
||||
|
||||
@@ -275,7 +275,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
||||
divClass.add " nsfw"
|
||||
|
||||
if not tweet.available:
|
||||
return buildHtml(tdiv(class=divClass & "unavailable timeline-item")):
|
||||
return buildHtml(tdiv(class=divClass & "unavailable timeline-item", data-username=tweet.user.username)):
|
||||
tdiv(class="unavailable-box"):
|
||||
if tweet.tombstone.len > 0:
|
||||
text tweet.tombstone
|
||||
@@ -297,7 +297,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
||||
tweet = tweet.retweet.get
|
||||
retweet = fullTweet.user.fullname
|
||||
|
||||
buildHtml(tdiv(class=("timeline-item " & divClass))):
|
||||
buildHtml(tdiv(class=("timeline-item " & divClass), data-username=tweet.user.username)):
|
||||
if not mainTweet:
|
||||
a(class="tweet-link", href=getLink(tweet))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user