diff --git a/src/formatters.nim b/src/formatters.nim index 06620d0..a616426 100644 --- a/src/formatters.nim +++ b/src/formatters.nim @@ -77,7 +77,7 @@ proc stripTwitterUrls*(text: string): string = proc proxifyVideo*(manifest: string; proxy: bool): string = proc cb(m: RegexMatch; s: string): string = result = "https://video.twimg.com" & s[m.group(0)[0]] - if proxy: result = result.getSigUrl("video") + if proxy: result = getVidUrl(result) result = manifest.replace(re"(.+(.ts|.m3u8|.vmap))", cb) proc getUserpic*(userpic: string; style=""): string = diff --git a/src/routes/media.nim b/src/routes/media.nim index 631aed2..e348d1b 100644 --- a/src/routes/media.nim +++ b/src/routes/media.nim @@ -12,24 +12,26 @@ export utils proc createMediaRouter*(cfg: Config) = router media: - get "/pic/@sig/@url": + get "/pic/@url": cond "http" in @"url" cond "twimg" in @"url" - let - uri = parseUri(decodeUrl(@"url")) - path = uri.path.split("/")[2 .. ^1].join("/") - filename = cfg.cacheDir / cleanFilename(path & uri.query) - if getHmac($uri) != @"sig": - resp showError("Failed to verify signature", cfg.title) + let uri = parseUri(decodeUrl(@"url")) + cond isTwitterUrl($uri) == true + + let path = uri.path.split("/")[2 .. ^1].join("/") + let filename = cfg.cacheDir / cleanFilename(path & uri.query) if not existsDir(cfg.cacheDir): createDir(cfg.cacheDir) if not existsFile(filename): let client = newAsyncHttpClient() - await client.downloadFile($uri, filename) - client.close() + try: + await client.downloadFile($uri, filename) + client.close() + except: + discard if not existsFile(filename): resp Http404 @@ -40,6 +42,27 @@ proc createMediaRouter*(cfg: Config) = resp buf, mimetype(filename) + get "/gif/@url": + cond "http" in @"url" + cond "twimg" in @"url" + cond "mp4" in @"url" or "gif" in @"url" + + let url = decodeUrl(@"url") + cond isTwitterUrl(url) == true + + let client = newAsyncHttpClient() + var content: string + try: + content = await client.getContent(url) + client.close + except: + discard + + if content.len == 0: + resp Http404 + + resp content, mimetype(url) + get "/video/@sig/@url": cond "http" in @"url" var url = decodeUrl(@"url") diff --git a/src/utils.nim b/src/utils.nim index d2ff70a..c1499bf 100644 --- a/src/utils.nim +++ b/src/utils.nim @@ -1,7 +1,15 @@ import strutils, strformat, sequtils, uri, tables import nimcrypto, regex -const key = "supersecretkey" +const + key = "supersecretkey" + twitterDomains = @[ + "twitter.com", + "twimg.com", + "abs.twimg.com", + "pbs.twimg.com", + "video.twimg.com" + ] proc mimetype*(filename: string): string = if ".png" in filename: @@ -16,11 +24,17 @@ proc mimetype*(filename: string): string = proc getHmac*(data: string): string = ($hmac(sha256, key, data))[0 .. 12] -proc getSigUrl*(link: string; path: string): string = +proc getVidUrl*(link: string): string = let sig = getHmac(link) url = encodeUrl(link) - &"/{path}/{sig}/{url}" + &"/video/{sig}/{url}" + +proc getGifUrl*(link: string): string = + &"/gif/{encodeUrl(link)}" + +proc getPicUrl*(link: string): string = + &"/pic/{encodeUrl(link)}" proc cleanFilename*(filename: string): string = const reg = re"[^A-Za-z0-9._-]" @@ -29,3 +43,6 @@ proc cleanFilename*(filename: string): string = proc filterParams*(params: Table): seq[(string, string)] = let filter = ["name", "id"] toSeq(params.pairs()).filterIt(it[0] notin filter) + +proc isTwitterUrl*(url: string): bool = + parseUri(url).hostname in twitterDomains diff --git a/src/views/general.nim b/src/views/general.nim index a06a2dc..08b492d 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -41,7 +41,7 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc=" meta(property="og:site_name", content="Twitter") for url in images: - meta(property="og:image", content=getSigUrl(url, "pic")) + meta(property="og:image", content=getPicUrl(url)) if video.len > 0: meta(property="og:video:url", content=video) diff --git a/src/views/profile.nim b/src/views/profile.nim index 3093d3a..8b70b51 100644 --- a/src/views/profile.nim +++ b/src/views/profile.nim @@ -14,7 +14,7 @@ proc renderStat(num, class: string; text=""): VNode = proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode = buildHtml(tdiv(class="profile-card")): tdiv(class="profile-card-info"): - let url = profile.getUserPic().getSigUrl("pic") + let url = getPicUrl(profile.getUserPic()) a(class="profile-card-avatar", href=url, target="_blank"): genImg(profile.getUserpic("_200x200")) @@ -72,7 +72,7 @@ proc renderBanner(profile: Profile): VNode = if "#" in profile.banner: tdiv(class="profile-banner-color", style={backgroundColor: profile.banner}) else: - a(href=getSigUrl(profile.banner, "pic"), target="_blank"): + a(href=getPicUrl(profile.banner), target="_blank"): genImg(profile.banner) proc renderProfile*(profile: Profile; timeline: Timeline; diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 2e2a0a5..23900ca 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -32,7 +32,7 @@ proc linkUser*(profile: Profile, class=""): VNode = proc genImg*(url: string; class=""): VNode = buildHtml(): - img(src=url.getSigUrl("pic"), class=class, alt="Image") + img(src=getPicUrl(url), class=class, alt="Image") proc linkText*(text: string; class=""): VNode = let url = if "http" notin text: "http://" & text else: text diff --git a/src/views/tweet.nim b/src/views/tweet.nim index b202005..d56e5de 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -41,7 +41,7 @@ proc renderAlbum(tweet: Tweet): VNode = tdiv(class="gallery-row", style={marginTop: margin}): for photo in photos: tdiv(class="attachment image"): - a(href=getSigUrl(photo & "?name=orig", "pic"), class="still-image", + a(href=getPicUrl(photo & "?name=orig"), class="still-image", target="_blank", style={display: flex}): genImg(photo) @@ -52,7 +52,7 @@ proc isPlaybackEnabled(prefs: Prefs; video: Video): bool = proc renderVideoDisabled(video: Video; path: string): VNode = buildHtml(tdiv): - img(src=video.thumb.getSigUrl("pic")) + img(src=getPicUrl(video.thumb)) tdiv(class="video-overlay"): case video.playbackType of mp4: @@ -62,7 +62,7 @@ proc renderVideoDisabled(video: Video; path: string): VNode = proc renderVideoUnavailable(video: Video): VNode = buildHtml(tdiv): - img(src=video.thumb.getSigUrl("pic")) + img(src=getPicUrl(video.thumb)) tdiv(class="video-overlay"): case video.reason of "dmcaed": @@ -74,13 +74,13 @@ proc renderVideo(video: Video; prefs: Prefs; path: string): VNode = buildHtml(tdiv(class="attachments")): tdiv(class="gallery-video"): tdiv(class="attachment video-container"): - let thumb = video.thumb.getSigUrl("pic") + let thumb = getPicUrl(video.thumb) if not video.available: renderVideoUnavailable(video) elif not prefs.isPlaybackEnabled(video): renderVideoDisabled(video, path) else: - let source = video.url.getSigUrl("video") + let source = getVidUrl(video.url) case video.playbackType of mp4: if prefs.muteVideos: @@ -99,8 +99,8 @@ proc renderGif(gif: Gif; prefs: Prefs): VNode = buildHtml(tdiv(class="attachments media-gif")): tdiv(class="gallery-gif", style=style(maxHeight, "unset")): tdiv(class="attachment"): - let thumb = gif.thumb.getSigUrl("pic") - let url = gif.url.getSigUrl("video") + let thumb = getPicUrl(gif.thumb) + let url = getGifUrl(gif.url) if prefs.autoplayGifs: video(class="gif", poster=thumb, autoplay="", muted="", loop=""): source(src=url, `type`="video/mp4") @@ -123,7 +123,7 @@ proc renderPoll(poll: Poll): VNode = proc renderCardImage(card: Card): VNode = buildHtml(tdiv(class="card-image-container")): tdiv(class="card-image"): - img(src=getSigUrl(get(card.image), "pic")) + img(src=getPicUrl(get(card.image))) if card.kind == player: tdiv(class="card-overlay"): tdiv(class="overlay-circle"):