From 62aa2f28237bddea311f9d16ca0a29ba50e7c4cc Mon Sep 17 00:00:00 2001 From: Zed Date: Mon, 17 Aug 2026 09:05:53 +0700 Subject: [PATCH] Parse user counts/bio from modern GraphQL shape --- src/experimental/parser/graphql.nim | 24 ++++++++++++++++++++++-- src/experimental/parser/user.nim | 8 +++----- src/experimental/types/graphuser.nim | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/experimental/parser/graphql.nim b/src/experimental/parser/graphql.nim index 0a7484c..85be202 100644 --- a/src/experimental/parser/graphql.nim +++ b/src/experimental/parser/graphql.nim @@ -10,11 +10,16 @@ proc parseUserResult*(userResult: UserResult): User = result.verifiedType = blue if result.username.len == 0 and userResult.core.screenName.len > 0: + # Modern UserByRestId/UserByScreenName shape: `legacy` is empty and the data + # lives in typed sub-objects (core/relationship_counts/tweet_counts/etc.). result.id = userResult.restId result.username = userResult.core.screenName result.fullname = userResult.core.name result.userPic = userResult.avatar.imageUrl.replace("_normal", "") + if userResult.banner.imageUrl.len > 0: + result.banner = userResult.banner.imageUrl & "/1500x500" + if userResult.privacy.isSome: result.protected = userResult.privacy.get.protected @@ -29,8 +34,23 @@ proc parseUserResult*(userResult: UserResult): User = if v.verifiedType != VerifiedType.none: result.verifiedType = v.verifiedType - if userResult.profileBio.isSome and result.bio.len == 0: - result.bio = userResult.profileBio.get.description + if userResult.relationshipCounts.isSome: + let rc = userResult.relationshipCounts.get + result.followers = rc.followers + result.following = rc.following + + if userResult.tweetCounts.isSome: + let tc = userResult.tweetCounts.get + result.tweets = tc.tweets + result.media = tc.mediaTweets + + if userResult.actionCounts.isSome: + result.likes = userResult.actionCounts.get.favoritesCount + + if userResult.profileBio.isSome: + let bio = userResult.profileBio.get + result.bio = bio.description + result.expandUserEntities(bio.entities) proc parseGraphUser*(json: string): User = if json.len == 0 or json[0] != '{': diff --git a/src/experimental/parser/user.nim b/src/experimental/parser/user.nim index 8b98e7f..866973c 100644 --- a/src/experimental/parser/user.nim +++ b/src/experimental/parser/user.nim @@ -11,10 +11,8 @@ let htRegex = nre.re"""(*U)(^|[^\w-_.?])([##$])([\w_]*+)(?!|">|#)""" htReplace = "$1$2$3" -proc expandUserEntities(user: var User; raw: RawUser) = - let - orig = user.bio.toRunes - ent = raw.entities +proc expandUserEntities*(user: var User; ent: Entities) = + let orig = user.bio.toRunes if ent.url.urls.len > 0: user.website = ent.url.urls[0].expandedUrl @@ -68,7 +66,7 @@ proc toUser*(raw: RawUser): User = if raw.pinnedTweetIdsStr.len > 0: result.pinnedTweet = parseBiggestInt(raw.pinnedTweetIdsStr[0]) - result.expandUserEntities(raw) + result.expandUserEntities(raw.entities) proc parseHook*(s: string; i: var int; v: var User) = var u: RawUser diff --git a/src/experimental/types/graphuser.nim b/src/experimental/types/graphuser.nim index 62c6612..ec41f89 100644 --- a/src/experimental/types/graphuser.nim +++ b/src/experimental/types/graphuser.nim @@ -1,5 +1,6 @@ import options, strutils from ../../types import User, VerifiedType +import user as userType # Entities, for modern profile_bio parsing type GraphUser* = object @@ -15,6 +16,7 @@ type UserBio* = object description*: string + entities*: Entities UserAvatar* = object imageUrl*: string @@ -28,18 +30,35 @@ type Privacy* = object protected*: bool + # Modern UserByRestId/UserByScreenName shape moves the counts out of `legacy` + # into these typed sub-objects. + RelationshipCounts* = object + followers*: int + following*: int + + TweetCounts* = object + tweets*: int + mediaTweets*: int + + ActionCounts* = object + favoritesCount*: int + UserResult* = object legacy*: User restId*: string isBlueVerified*: bool core*: UserCore avatar*: UserAvatar + banner*: UserAvatar unavailableReason*: Option[string] reason*: Option[string] privacy*: Option[Privacy] profileBio*: Option[UserBio] verification*: Option[Verification] location*: Option[Location] + relationshipCounts*: Option[RelationshipCounts] + tweetCounts*: Option[TweetCounts] + actionCounts*: Option[ActionCounts] proc enumHook*(s: string; v: var VerifiedType) = v = try: