nitter/src/views/profile.nim
Markus Unterwaditzer 7622db80b9 add rel=me support
This is to support link verification in Mastodon.

Mastodon does not have any equivalent to Twitter's blue-tick, so if you
want to decorate your profile with some sense of authenticity, you can
verify your profile to be associated with a particular URL: https://docs.joinmastodon.org/user/profile/#verification

GitHub gained native support for this sort of verification sometime
between last November and now, and so when you have a bidirectional link
between your GitHub profile and Mastodon profile, the link to GitHub
will be green.

However, Twitter does not support rel=me, and is unlikely to.

With this change, one can link their profile in their Mastodon bio using
nitter.net/username, and for as long as their mastodon profile is linked
back in the Twitter "URL" field, it will be verified.

There have been similar attempts with "trusted" proxy URLs in the past,
i.e. Twitter profile mirrors that are set up for the sole purpose of
supporting rel=me verification. Those proxies are however unknown to
most people, and the entire domain-based verification system kind of
breaks down if nobody knows whether the domain can be trusted.

People know nitter.net, however, and trust what is written on there to
match what is written on twitter.com [1]

Who knows whether the same is true for the unofficial Nitter instances,
but I think one can ignore them for this purpose. Of course I can deploy
this fork today on nitter.woodland.cafe and then verify
`@untitaker@woodland.cafe` against `nitter.woodland.cafe/untitaker`, but
that's kind of pointless.

[1] Whether it is really *known* may be debatable, but of all Twitter
    mirrors/proxies, I think nitter.net and maybe fxtwitter.com have the
    highest domain reputation and the best chance at making this kind of
    verification system work for Twitter.
2023-10-04 19:48:08 +02:00

120 lines
4 KiB
Nim

# SPDX-License-Identifier: AGPL-3.0-only
import strutils, strformat
import karax/[karaxdsl, vdom, vstyles]
import renderutils, search
import ".."/[types, utils, formatters]
proc renderStat(num: int; class: string; text=""): VNode =
let t = if text.len > 0: text else: class
buildHtml(li(class=class)):
span(class="profile-stat-header"): text capitalizeAscii(t)
span(class="profile-stat-num"):
text insertSep($num, ',')
proc renderUserCard*(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="profile-card")):
tdiv(class="profile-card-info"):
let
url = getPicUrl(user.getUserPic())
size =
if prefs.autoplayGifs and user.userPic.endsWith("gif"): ""
else: "_400x400"
a(class="profile-card-avatar", href=url, target="_blank"):
genImg(user.getUserPic(size))
tdiv(class="profile-card-tabs-name"):
linkUser(user, class="profile-card-fullname")
linkUser(user, class="profile-card-username")
tdiv(class="profile-card-extra"):
if user.bio.len > 0:
tdiv(class="profile-bio"):
p(dir="auto"):
verbatim replaceUrls(user.bio, prefs)
if user.location.len > 0:
tdiv(class="profile-location"):
span: icon "location"
let (place, url) = getLocation(user)
if url.len > 1:
a(href=url): text place
elif "://" in place:
a(href=place): text place
else:
span: text place
if user.website.len > 0:
tdiv(class="profile-website"):
span:
let url = replaceUrls(user.website, prefs)
icon "link"
a(rel="me", href=url): text url.shortLink
tdiv(class="profile-joindate"):
span(title=getJoinDateFull(user)):
icon "calendar", getJoinDate(user)
tdiv(class="profile-card-extra-links"):
ul(class="profile-statlist"):
renderStat(user.tweets, "posts", text="Tweets")
renderStat(user.following, "following")
renderStat(user.followers, "followers")
renderStat(user.likes, "likes")
proc renderPhotoRail(profile: Profile): VNode =
let count = insertSep($profile.user.media, ',')
buildHtml(tdiv(class="photo-rail-card")):
tdiv(class="photo-rail-header"):
a(href=(&"/{profile.user.username}/media")):
icon "picture", count & " Photos and videos"
input(id="photo-rail-grid-toggle", `type`="checkbox")
label(`for`="photo-rail-grid-toggle", class="photo-rail-header-mobile"):
icon "picture", count & " Photos and videos"
icon "down"
tdiv(class="photo-rail-grid"):
for i, photo in profile.photoRail:
if i == 16: break
let photoSuffix =
if "format" in photo.url or "placeholder" in photo.url: ""
else: ":thumb"
a(href=(&"/{profile.user.username}/status/{photo.tweetId}#m")):
genImg(photo.url & photoSuffix)
proc renderBanner(banner: string): VNode =
buildHtml():
if banner.len == 0:
a()
elif banner.startsWith('#'):
a(style={backgroundColor: banner})
else:
a(href=getPicUrl(banner), target="_blank"): genImg(banner)
proc renderProtected(username: string): VNode =
buildHtml(tdiv(class="timeline-container")):
tdiv(class="timeline-header timeline-protected"):
h2: text "This account's tweets are protected."
p: text &"Only confirmed followers have access to @{username}'s tweets."
proc renderProfile*(profile: var Profile; prefs: Prefs; path: string): VNode =
profile.tweets.query.fromUser = @[profile.user.username]
buildHtml(tdiv(class="profile-tabs")):
if not prefs.hideBanner:
tdiv(class="profile-banner"):
renderBanner(profile.user.banner)
let sticky = if prefs.stickyProfile: " sticky" else: ""
tdiv(class=("profile-tab" & sticky)):
renderUserCard(profile.user, prefs)
if profile.photoRail.len > 0:
renderPhotoRail(profile)
if profile.user.protected:
renderProtected(profile.user.username)
else:
renderTweetSearch(profile.tweets, prefs, path, profile.pinned)