This commit is contained in:
Zed 2019-08-15 23:17:13 +02:00
parent 6a7a65e16b
commit 1464131707
3 changed files with 48 additions and 57 deletions

View file

@ -37,8 +37,7 @@ proc showSingleTimeline(name, after, agent: string; query: Option[Query];
return "" return ""
let profileHtml = renderProfile(profile, timeline, await railFut, prefs) let profileHtml = renderProfile(profile, timeline, await railFut, prefs)
return renderMain(profileHtml, prefs, title=cfg.title, titleText=pageTitle(profile), return renderMain(profileHtml, cfg.title, pageTitle(profile), pageDesc(profile))
desc=pageDesc(profile))
proc showMultiTimeline(names: seq[string]; after, agent: string; query: Option[Query]; proc showMultiTimeline(names: seq[string]; after, agent: string; query: Option[Query];
prefs: Prefs): Future[string] {.async.} = prefs: Prefs): Future[string] {.async.} =
@ -51,7 +50,7 @@ proc showMultiTimeline(names: seq[string]; after, agent: string; query: Option[Q
var timeline = renderMulti(await getTimelineSearch(get(q), after, agent), var timeline = renderMulti(await getTimelineSearch(get(q), after, agent),
names.join(","), prefs) names.join(","), prefs)
return renderMain(timeline, prefs, title=cfg.title, titleText="Multi") return renderMain(timeline, cfg.title, "Multi")
proc showTimeline(name, after: string; query: Option[Query]; proc showTimeline(name, after: string; query: Option[Query];
prefs: Prefs): Future[string] {.async.} = prefs: Prefs): Future[string] {.async.} =
@ -65,10 +64,10 @@ proc showTimeline(name, after: string; query: Option[Query];
template respTimeline(timeline: typed) = template respTimeline(timeline: typed) =
if timeline.len == 0: if timeline.len == 0:
resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title, prefs) resp Http404, showError("User \"" & @"name" & "\" not found", cfg.title)
resp timeline resp timeline
proc getCookiePrefs(request: Request): Prefs = template cookiePrefs(): untyped {.dirty.} =
getPrefs(request.cookies.getOrDefault("preferences")) getPrefs(request.cookies.getOrDefault("preferences"))
setProfileCacheTime(cfg.profileCacheTime) setProfileCacheTime(cfg.profileCacheTime)
@ -80,59 +79,55 @@ settings:
routes: routes:
get "/": get "/":
let prefs = getCookiePrefs(request) resp renderMain(renderSearch(), cfg.title)
resp renderMain(renderSearch(), prefs, title=cfg.title)
post "/search": post "/search":
if @"query".len == 0: if @"query".len == 0:
resp Http404, showError("Please enter a username.", cfg.title, resp Http404, showError("Please enter a username.", cfg.title)
getCookiePrefs(request))
redirect("/" & @"query") redirect("/" & @"query")
post "/saveprefs": post "/saveprefs":
var prefs = getCookiePrefs(request) var prefs = cookiePrefs()
genUpdatePrefs() genUpdatePrefs()
setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=true) setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=true)
redirect("/settings") redirect("/")
post "/resetprefs": post "/resetprefs":
var prefs = getCookiePrefs(request) var prefs = cookiePrefs()
resetPrefs(prefs) resetPrefs(prefs)
setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=true) setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=true)
redirect("/settings") redirect("/settings")
get "/settings": get "/settings":
let prefs = getCookiePrefs(request) resp renderMain(renderPreferences(cookiePrefs()), cfg.title, "Preferences")
resp renderMain(renderPreferences(prefs), prefs, title=cfg.title, titleText="Preferences")
get "/@name/?": get "/@name/?":
cond '.' notin @"name" cond '.' notin @"name"
let prefs = getCookiePrefs(request) respTimeline(await showTimeline(@"name", @"after", none(Query), cookiePrefs()))
respTimeline(await showTimeline(@"name", @"after", none(Query), prefs))
get "/@name/search": get "/@name/search":
cond '.' notin @"name" cond '.' notin @"name"
let prefs = getCookiePrefs(request) let prefs = cookiePrefs()
let query = initQuery(@"filter", @"include", @"not", @"sep", @"name") let query = initQuery(@"filter", @"include", @"not", @"sep", @"name")
respTimeline(await showTimeline(@"name", @"after", some(query), prefs)) respTimeline(await showTimeline(@"name", @"after", some(query), cookiePrefs()))
get "/@name/replies": get "/@name/replies":
cond '.' notin @"name" cond '.' notin @"name"
let prefs = getCookiePrefs(request) let prefs = cookiePrefs()
respTimeline(await showTimeline(@"name", @"after", some(getReplyQuery(@"name")), prefs)) respTimeline(await showTimeline(@"name", @"after", some(getReplyQuery(@"name")), cookiePrefs()))
get "/@name/media": get "/@name/media":
cond '.' notin @"name" cond '.' notin @"name"
let prefs = getCookiePrefs(request) let prefs = cookiePrefs()
respTimeline(await showTimeline(@"name", @"after", some(getMediaQuery(@"name")), prefs)) respTimeline(await showTimeline(@"name", @"after", some(getMediaQuery(@"name")), cookiePrefs()))
get "/@name/status/@id": get "/@name/status/@id":
cond '.' notin @"name" cond '.' notin @"name"
let prefs = getCookiePrefs(request) let prefs = cookiePrefs()
let conversation = await getTweet(@"name", @"id", getAgent()) let conversation = await getTweet(@"name", @"id", getAgent())
if conversation == nil or conversation.tweet.id.len == 0: if conversation == nil or conversation.tweet.id.len == 0:
resp Http404, showError("Tweet not found", cfg.title, prefs) resp Http404, showError("Tweet not found", cfg.title)
let title = pageTitle(conversation.tweet.profile) let title = pageTitle(conversation.tweet.profile)
let desc = conversation.tweet.text let desc = conversation.tweet.text
@ -141,29 +136,26 @@ routes:
if conversation.tweet.video.isSome(): if conversation.tweet.video.isSome():
let thumb = get(conversation.tweet.video).thumb let thumb = get(conversation.tweet.video).thumb
let vidUrl = getVideoEmbed(conversation.tweet.id) let vidUrl = getVideoEmbed(conversation.tweet.id)
resp renderMain(html, prefs, title=cfg.title, titleText=title, desc=desc, resp renderMain(html, cfg.title, title, desc, images = @[thumb],
images = @[thumb], `type`="video", video=vidUrl) `type`="video", video=vidUrl)
elif conversation.tweet.gif.isSome(): elif conversation.tweet.gif.isSome():
let thumb = get(conversation.tweet.gif).thumb let thumb = get(conversation.tweet.gif).thumb
let vidUrl = getVideoEmbed(conversation.tweet.id) let vidUrl = getVideoEmbed(conversation.tweet.id)
resp renderMain(html, prefs, title=cfg.title, titleText=title, desc=desc, resp renderMain(html, cfg.title, title, desc, images = @[thumb],
images = @[thumb], `type`="video", video=vidUrl) `type`="video", video=vidUrl)
else: else:
resp renderMain(html, prefs, title=cfg.title, titleText=title, resp renderMain(html, cfg.title, title, desc, images=conversation.tweet.photos)
desc=desc, images=conversation.tweet.photos)
get "/pic/@sig/@url": get "/pic/@sig/@url":
cond "http" in @"url" cond "http" in @"url"
cond "twimg" in @"url" cond "twimg" in @"url"
let prefs = getCookiePrefs(request)
let let
uri = parseUri(decodeUrl(@"url")) uri = parseUri(decodeUrl(@"url"))
path = uri.path.split("/")[2 .. ^1].join("/") path = uri.path.split("/")[2 .. ^1].join("/")
filename = cfg.cacheDir / cleanFilename(path & uri.query) filename = cfg.cacheDir / cleanFilename(path & uri.query)
if getHmac($uri) != @"sig": if getHmac($uri) != @"sig":
resp showError("Failed to verify signature", cfg.title, prefs) resp showError("Failed to verify signature", cfg.title)
if not existsDir(cfg.cacheDir): if not existsDir(cfg.cacheDir):
createDir(cfg.cacheDir) createDir(cfg.cacheDir)
@ -185,11 +177,10 @@ routes:
get "/video/@sig/@url": get "/video/@sig/@url":
cond "http" in @"url" cond "http" in @"url"
cond "video.twimg" in @"url" cond "video.twimg" in @"url"
let prefs = getCookiePrefs(request)
let url = decodeUrl(@"url") let url = decodeUrl(@"url")
if getHmac(url) != @"sig": if getHmac(url) != @"sig":
resp showError("Failed to verify signature", cfg.title, prefs) resp showError("Failed to verify signature", cfg.title)
let client = newAsyncHttpClient() let client = newAsyncHttpClient()
let video = await client.getContent(url) let video = await client.getContent(url)

View file

@ -17,7 +17,7 @@ proc renderNavbar*(title: string): VNode =
icon "info-circled", title="About", href="/about" icon "info-circled", title="About", href="/about"
icon "cog-2", title="Preferences", href="/settings" icon "cog-2", title="Preferences", href="/settings"
proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc=""; proc renderMain*(body: VNode; title="Nitter"; titleText=""; desc="";
`type`="article"; video=""; images: seq[string] = @[]): string = `type`="article"; video=""; images: seq[string] = @[]): string =
let node = buildHtml(html(lang="en")): let node = buildHtml(html(lang="en")):
head: head:
@ -62,5 +62,5 @@ proc renderError*(error: string): VNode =
tdiv(class="error-panel"): tdiv(class="error-panel"):
span: text error span: text error
proc showError*(error: string; title: string; prefs: Prefs): string = proc showError*(error, title: string): string =
renderMain(renderError(error), prefs, title=title, titleText="Error") renderMain(renderError(error), title, "Error")

View file

@ -6,62 +6,62 @@ card = [
['voidtarget/status/1133028231672582145', ['voidtarget/status/1133028231672582145',
'sinkingsugar/nimqt-example', 'sinkingsugar/nimqt-example',
'A sample of a Qt app written using mostly nim. Contribute to sinkingsugar/nimqt-example development by creating an account on GitHub.', 'A sample of a Qt app written using mostly nim. Contribute to sinkingsugar/nimqt-example development by creating an account on GitHub.',
'github.com', '-tb6lD-A', False], 'github.com', False],
['Bountysource/status/1141879700639215617', ['Bountysource/status/1141879700639215617',
'$1,000 Bounty on kivy/plyer', '$1,000 Bounty on kivy/plyer',
'Automation and Screen Reader Support', 'Automation and Screen Reader Support',
'bountysource.com', '1161324818224078848', False], 'bountysource.com', False],
['lorenlugosch/status/1115440394148487168', ['lorenlugosch/status/1115440394148487168',
'lorenlugosch/pretrain_speech_model', 'lorenlugosch/pretrain_speech_model',
'Speech Model Pre-training for End-to-End Spoken Language Understanding - lorenlugosch/pretrain_speech_model', 'Speech Model Pre-training for End-to-End Spoken Language Understanding - lorenlugosch/pretrain_speech_model',
'github.com', '1161172194040246272', False], 'github.com', False],
['PyTorch/status/1123379369672450051', ['PyTorch/status/1123379369672450051',
'PyTorch', 'PyTorch',
'An open source deep learning platform that provides a seamless path from research prototyping to production deployment.', 'An open source deep learning platform that provides a seamless path from research prototyping to production deployment.',
'pytorch.org', 'lAc4aESh', False], 'pytorch.org', False],
['Thom_Wolf/status/1122466524860702729', ['Thom_Wolf/status/1122466524860702729',
'pytorch/fairseq', 'pytorch/fairseq',
'Facebook AI Research Sequence-to-Sequence Toolkit written in Python. - pytorch/fairseq', 'Facebook AI Research Sequence-to-Sequence Toolkit written in Python. - pytorch/fairseq',
'github.com', '1SVn24P6', False], 'github.com', False],
['TheTwoffice/status/558685306090946561', ['TheTwoffice/status/558685306090946561',
'Eternity: a moment standing still forever…', 'Eternity: a moment standing still forever…',
'- James Montgomery. | facebook | 500px | ferpectshotz | I dusted off this one from my old archives, it was taken while I was living in mighty new York city working at Wall St. I think this was the 11...', '- James Montgomery. | facebook | 500px | ferpectshotz | I dusted off this one from my old archives, it was taken while I was living in mighty new York city working at Wall St. I think this was the 11...',
'flickr.com', '161236662619389953', True], 'flickr.com', True],
['nim_lang/status/1136652293510717440', ['nim_lang/status/1136652293510717440',
'Version 0.20.0 released', 'Version 0.20.0 released',
'We are very proud to announce Nim version 0.20. This is a massive release, both literally and figuratively. It contains more than 1,000 commits and it marks our release candidate for version 1.0!', 'We are very proud to announce Nim version 0.20. This is a massive release, both literally and figuratively. It contains more than 1,000 commits and it marks our release candidate for version 1.0!',
'nim-lang.org', 'Q0aJrdMZ', True], 'nim-lang.org', True],
['Tesla/status/1141041022035623936', ['Tesla/status/1141041022035623936',
'Experience the Tesla Arcade', 'Experience the Tesla Arcade',
'', '',
'www.tesla.com', '40H36baw', True], 'www.tesla.com', True],
['mobile_test/status/490378953744318464', ['mobile_test/status/490378953744318464',
'Nantasket Beach', 'Nantasket Beach',
'Rocks on the beach.', 'Rocks on the beach.',
'500px.com', 'FVUU4YDwN', True], '500px.com', True],
['voidtarget/status/1094632512926605312', ['voidtarget/status/1094632512926605312',
'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too)', 'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too)',
'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too) - obsplugin.nim', 'Basic OBS Studio plugin, written in nim, supporting C++ (C fine too) - obsplugin.nim',
'gist.github.com', '1160647657574076423', True], 'gist.github.com', True],
['AdsAPI/status/1110272721005367296', ['AdsAPI/status/1110272721005367296',
'Conversation Targeting', 'Conversation Targeting',
'', '',
'view.highspot.com', 'FrVMLWJH', True], 'view.highspot.com', True],
['FluentAI/status/1116417904831029248', ['FluentAI/status/1116417904831029248',
'Amazons Alexa isnt just AI — thousands of humans are listening', 'Amazons Alexa isnt just AI — thousands of humans are listening',
'One of the only ways to improve Alexa is to have human beings check it for errors', 'One of the only ways to improve Alexa is to have human beings check it for errors',
'theverge.com', 'HOW73fOB', True] 'theverge.com', True]
] ]
no_thumb = [ no_thumb = [
@ -80,17 +80,17 @@ playable = [
['nim_lang/status/1118234460904919042', ['nim_lang/status/1118234460904919042',
'Nim development blog 2019-03', 'Nim development blog 2019-03',
'Arne (aka Krux02) * debugging: * improved nim-gdb, $ works, framefilter * alias for --debugger:native: -g * bugs: * forwarding of .pure. * sizeof union * fea...', 'Arne (aka Krux02) * debugging: * improved nim-gdb, $ works, framefilter * alias for --debugger:native: -g * bugs: * forwarding of .pure. * sizeof union * fea...',
'youtube.com', '1161613174514290688'], 'youtube.com'],
['nim_lang/status/1121090879823986688', ['nim_lang/status/1121090879823986688',
'Nim - First natively compiled language w/ hot code-reloading at...', 'Nim - First natively compiled language w/ hot code-reloading at...',
'#nim #c++ #ACCUConf Nim is a statically typed systems and applications programming language which offers perhaps some of the most powerful metaprogramming ca...', '#nim #c++ #ACCUConf Nim is a statically typed systems and applications programming language which offers perhaps some of the most powerful metaprogramming ca...',
'youtube.com', '1161379576087568386'], 'youtube.com'],
['lele/status/819930645145288704', ['lele/status/819930645145288704',
'Eurocrash presents Open Decks - emerging dj #4: E-Musik', 'Eurocrash presents Open Decks - emerging dj #4: E-Musik',
"OPEN DECKS is Eurocrash's new project about discovering new and emerging dj talents. Every selected dj will have the chance to perform the first dj-set in front of an actual audience. The best dj...", "OPEN DECKS is Eurocrash's new project about discovering new and emerging dj talents. Every selected dj will have the chance to perform the first dj-set in front of an actual audience. The best dj...",
'mixcloud.com', '161048988763795457'] 'mixcloud.com']
] ]
promo = [ promo = [
@ -106,12 +106,12 @@ promo = [
class CardTest(BaseTestCase): class CardTest(BaseTestCase):
@parameterized.expand(card) @parameterized.expand(card)
def test_card(self, tweet, title, description, destination, image, large): def test_card(self, tweet, title, description, destination, large):
self.open_nitter(tweet) self.open_nitter(tweet)
card = Card(Conversation.main + " ") card = Card(Conversation.main + " ")
self.assert_text(title, card.title) self.assert_text(title, card.title)
self.assert_text(destination, card.destination) self.assert_text(destination, card.destination)
self.assertIn(image, self.get_image_url(card.image + ' img')) self.assertIn('_img', self.get_image_url(card.image + ' img'))
if len(description) > 0: if len(description) > 0:
self.assert_text(description, card.description) self.assert_text(description, card.description)
if large: if large:
@ -129,12 +129,12 @@ class CardTest(BaseTestCase):
self.assert_text(description, card.description) self.assert_text(description, card.description)
@parameterized.expand(playable) @parameterized.expand(playable)
def test_card_playable(self, tweet, title, description, destination, image): def test_card_playable(self, tweet, title, description, destination):
self.open_nitter(tweet) self.open_nitter(tweet)
card = Card(Conversation.main + " ") card = Card(Conversation.main + " ")
self.assert_text(title, card.title) self.assert_text(title, card.title)
self.assert_text(destination, card.destination) self.assert_text(destination, card.destination)
self.assertIn(image, self.get_image_url(card.image + ' img')) self.assertIn('_img', self.get_image_url(card.image + ' img'))
self.assert_element_visible('.card-overlay') self.assert_element_visible('.card-overlay')
if len(description) > 0: if len(description) > 0:
self.assert_text(description, card.description) self.assert_text(description, card.description)