From f8254c2f0f3bfacb754d7ad69be9b55258c8337c Mon Sep 17 00:00:00 2001 From: Zed Date: Sat, 25 Nov 2023 10:06:12 +0000 Subject: [PATCH] Add support for business and gov verification Also improve icon rendering on Firefox --- src/consts.nim | 10 ++++------ src/experimental/parser/graphql.nim | 5 +++-- src/experimental/parser/unifiedcard.nim | 3 +-- src/experimental/parser/user.nim | 2 +- src/experimental/types/user.nim | 4 ++-- src/parser.nim | 6 +++--- src/redis_cache.nim | 1 + src/sass/include/_variables.scss | 2 ++ src/sass/index.scss | 21 ++++++++++++++++++--- src/sass/search.scss | 2 ++ src/types.nim | 12 ++++++++---- src/views/general.nim | 2 +- src/views/renderutils.nim | 17 ++++++++++++----- src/views/tweet.nim | 3 +-- 14 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/consts.nim b/src/consts.nim index d3a3d80..e1c35e6 100644 --- a/src/consts.nim +++ b/src/consts.nim @@ -29,12 +29,10 @@ const "include_cards": "1", "include_entities": "1", "include_profile_interstitial_type": "0", - "include_quote_count": "1", - "include_reply_count": "1", - "include_user_entities": "1", - "include_ext_reply_count": "1", - "include_ext_is_blue_verified": "1", - # "include_ext_verified_type": "1", + "include_quote_count": "0", + "include_reply_count": "0", + "include_user_entities": "0", + "include_ext_reply_count": "0", "include_ext_media_color": "0", "cards_platform": "Web-13", "tweet_mode": "extended", diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 0e9a678..c7f115f 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -1,7 +1,7 @@ import options import jsony import user, ../types/[graphuser, graphlistmembers] -from ../../types import User, Result, Query, QueryKind +from ../../types import User, VerifiedType, Result, Query, QueryKind proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': @@ -14,7 +14,8 @@ proc parseGraphUser*(json: string): User = result = raw.data.userResult.result.legacy result.id = raw.data.userResult.result.restId - result.verified = result.verified or raw.data.userResult.result.isBlueVerified + if result.verifiedType == none and raw.data.userResult.result.isBlueVerified: + result.verifiedType = blue proc parseGraphListMembers*(json, cursor: string): Result[User] = result = Result[User]( diff --git a/src/experimental/parser/unifiedcard.nim b/src/experimental/parser/unifiedcard.nim index 1f7b825..a112974 100644 --- a/src/experimental/parser/unifiedcard.nim +++ b/src/experimental/parser/unifiedcard.nim @@ -1,7 +1,6 @@ import std/[options, tables, strutils, strformat, sugar] import jsony -import user -import ../types/unifiedcard +import user, ../types/unifiedcard from ../../types import Card, CardKind, Video from ../../utils import twimg, https diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 78f596e..07e0477 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -56,7 +56,7 @@ proc toUser*(raw: RawUser): User = tweets: raw.statusesCount, likes: raw.favouritesCount, media: raw.mediaCount, - verified: raw.verified or raw.extIsBlueVerified, + verifiedType: raw.verifiedType, protected: raw.protected, joinDate: parseTwitterDate(raw.createdAt), banner: getBanner(raw), diff --git a/src/experimental/types/user.nim b/src/experimental/types/user.nim index 39331a0..7dc0194 100644 --- a/src/experimental/types/user.nim +++ b/src/experimental/types/user.nim @@ -1,5 +1,6 @@ import options import common +from ../../types import VerifiedType type RawUser* = object @@ -15,8 +16,7 @@ type favouritesCount*: int statusesCount*: int mediaCount*: int - verified*: bool - extIsBlueVerified*: bool + verifiedType*: VerifiedType protected*: bool profileLinkColor*: string profileBannerUrl*: string diff --git a/src/parser.nim b/src/parser.nim index d7ba613..a7bf89d 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -21,7 +21,7 @@ proc parseUser(js: JsonNode; id=""): User = tweets: js{"statuses_count"}.getInt, likes: js{"favourites_count"}.getInt, media: js{"media_count"}.getInt, - verified: js{"verified"}.getBool or js{"ext_is_blue_verified"}.getBool, + verifiedType: parseEnum[VerifiedType](js{"verified_type"}.getStr("None")), protected: js{"protected"}.getBool, joinDate: js{"created_at"}.getTime ) @@ -34,8 +34,8 @@ proc parseGraphUser(js: JsonNode): User = user = ? js{"user_results", "result"} result = parseUser(user{"legacy"}) - if "is_blue_verified" in user: - result.verified = user{"is_blue_verified"}.getBool() + if result.verifiedType == none and user{"is_blue_verified"}.getBool(false): + result.verifiedType = blue proc parseGraphList*(js: JsonNode): List = if js.isNull: return diff --git a/src/redis_cache.nim b/src/redis_cache.nim index a8b5ff8..1d77cca 100644 --- a/src/redis_cache.nim +++ b/src/redis_cache.nim @@ -52,6 +52,7 @@ proc initRedisPool*(cfg: Config) {.async.} = await migrate("profileDates", "p:*") await migrate("profileStats", "p:*") await migrate("userType", "p:*") + await migrate("verifiedType", "p:*") pool.withAcquire(r): # optimize memory usage for user ID buckets diff --git a/src/sass/include/_variables.scss b/src/sass/include/_variables.scss index 0f81235..0c95ff6 100644 --- a/src/sass/include/_variables.scss +++ b/src/sass/include/_variables.scss @@ -28,6 +28,8 @@ $more_replies_dots: #AD433B; $error_red: #420A05; $verified_blue: #1DA1F2; +$verified_business: #FAC82B; +$verified_government: #C1B6A4; $icon_text: $fg_color; $tab: $fg_color; diff --git a/src/sass/index.scss b/src/sass/index.scss index 9e2e347..6cab48e 100644 --- a/src/sass/index.scss +++ b/src/sass/index.scss @@ -39,6 +39,8 @@ body { --error_red: #{$error_red}; --verified_blue: #{$verified_blue}; + --verified_business: #{$verified_business}; + --verified_government: #{$verified_government}; --icon_text: #{$icon_text}; --tab: #{$fg_color}; @@ -141,17 +143,30 @@ ul { .verified-icon { color: var(--icon_text); - background-color: var(--verified_blue); border-radius: 50%; flex-shrink: 0; margin: 2px 0 3px 3px; - padding-top: 2px; - height: 12px; + padding-top: 3px; + height: 11px; width: 14px; font-size: 8px; display: inline-block; text-align: center; vertical-align: middle; + + &.blue { + background-color: var(--verified_blue); + } + + &.business { + color: var(--bg_panel); + background-color: var(--verified_business); + } + + &.government { + color: var(--bg_panel); + background-color: var(--verified_government); + } } @media(max-width: 600px) { diff --git a/src/sass/search.scss b/src/sass/search.scss index 0311fb0..f70f7ea 100644 --- a/src/sass/search.scss +++ b/src/sass/search.scss @@ -14,6 +14,8 @@ button { margin: 0 2px 0 0; height: 23px; + display: flex; + align-items: center; } .pref-input { diff --git a/src/types.nim b/src/types.nim index bc791b1..ddbebdf 100644 --- a/src/types.nim +++ b/src/types.nim @@ -10,9 +10,7 @@ type BadClientError* = object of CatchableError TimelineKind* {.pure.} = enum - tweets - replies - media + tweets, replies, media Api* {.pure.} = enum tweetDetail @@ -63,6 +61,12 @@ type tweetUnavailable = 421 tweetCensored = 422 + VerifiedType* = enum + none = "None" + blue = "Blue" + business = "Business" + government = "Government" + User* = object id*: string username*: string @@ -78,7 +82,7 @@ type tweets*: int likes*: int media*: int - verified*: bool + verifiedType*: VerifiedType protected*: bool suspended*: bool joinDate*: DateTime diff --git a/src/views/general.nim b/src/views/general.nim index 5e96d02..87d30f2 100644 --- a/src/views/general.nim +++ b/src/views/general.nim @@ -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: diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index 9dffdcb..451ddfb 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -23,6 +23,13 @@ proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode = if text.len > 0: text " " & text +template verifiedIcon*(user: User): untyped {.dirty.} = + if user.verifiedType != none: + let lower = ($user.verifiedType).toLowerAscii() + icon "ok", class=(&"verified-icon {lower}"), title=(&"Verified {lower} account") + else: + text "" + proc linkUser*(user: User, class=""): VNode = let isName = "username" notin class @@ -32,11 +39,11 @@ proc linkUser*(user: User, class=""): VNode = buildHtml(a(href=href, class=class, title=nameText)): text nameText - if isName and user.verified: - icon "ok", class="verified-icon", title="Verified account" - if isName and user.protected: - text " " - icon "lock", title="Protected account" + if isName: + verifiedIcon(user) + if user.protected: + text " " + icon "lock", title="Protected account" proc linkText*(text: string; class=""): VNode = let url = if "http" notin text: https & text else: text diff --git a/src/views/tweet.nim b/src/views/tweet.nim index f47ae9a..2fe4ac9 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -200,8 +200,7 @@ proc renderAttribution(user: User; prefs: Prefs): VNode = buildHtml(a(class="attribution", href=("/" & user.username))): renderMiniAvatar(user, prefs) strong: text user.fullname - if user.verified: - icon "ok", class="verified-icon", title="Verified account" + verifiedIcon(user) proc renderMediaTags(tags: seq[User]): VNode = buildHtml(tdiv(class="media-tag-block")):