|
|
@@ -4,9 +4,9 @@ import packedjson, packedjson/deserialiser
|
|
|
import types, parserutils, utils
|
|
|
import experimental/parser/unifiedcard
|
|
|
|
|
|
-proc parseProfile(js: JsonNode; id=""): Profile =
|
|
|
+proc parseUser(js: JsonNode; id=""): User =
|
|
|
if js.isNull: return
|
|
|
- result = Profile(
|
|
|
+ result = User(
|
|
|
id: if id.len > 0: id else: js{"id_str"}.getStr,
|
|
|
username: js{"screen_name"}.getStr,
|
|
|
fullname: js{"name"}.getStr,
|
|
|
@@ -24,7 +24,17 @@ proc parseProfile(js: JsonNode; id=""): Profile =
|
|
|
joinDate: js{"created_at"}.getTime
|
|
|
)
|
|
|
|
|
|
- result.expandProfileEntities(js)
|
|
|
+ result.expandUserEntities(js)
|
|
|
+
|
|
|
+proc parseGraphUser*(js: JsonNode; id: string): User =
|
|
|
+ if js.isNull: return
|
|
|
+
|
|
|
+ with user, js{"data", "user", "result", "legacy"}:
|
|
|
+ result = parseUser(user, id)
|
|
|
+
|
|
|
+ with pinned, user{"pinned_tweet_ids_str"}:
|
|
|
+ if pinned.kind == JArray and pinned.len > 0:
|
|
|
+ result.pinnedTweet = parseBiggestInt(pinned[0].getStr)
|
|
|
|
|
|
proc parseGraphList*(js: JsonNode): List =
|
|
|
if js.isNull: return
|
|
|
@@ -45,21 +55,30 @@ proc parseGraphList*(js: JsonNode): List =
|
|
|
banner: list{"custom_banner_media", "media_info", "url"}.getImageStr
|
|
|
)
|
|
|
|
|
|
-proc parseListMembers*(js: JsonNode; cursor: string): Result[Profile] =
|
|
|
- result = Result[Profile](
|
|
|
+proc parseGraphListMembers*(js: JsonNode; cursor: string): Result[User] =
|
|
|
+ result = Result[User](
|
|
|
beginning: cursor.len == 0,
|
|
|
query: Query(kind: userList)
|
|
|
)
|
|
|
|
|
|
if js.isNull: return
|
|
|
|
|
|
- result.top = js{"previous_cursor_str"}.getStr
|
|
|
- result.bottom = js{"next_cursor_str"}.getStr
|
|
|
- if result.bottom.len == 1:
|
|
|
- result.bottom.setLen 0
|
|
|
+ # result.top = js{"previous_cursor_str"}.getStr
|
|
|
+ # result.bottom = js{"next_cursor_str"}.getStr
|
|
|
+ # if result.bottom.len == 1:
|
|
|
+ # result.bottom.setLen 0
|
|
|
+
|
|
|
+ let root = js{"data", "list", "members_timeline", "timeline", "instructions"}
|
|
|
+ for instruction in root:
|
|
|
+ if instruction{"type"}.getStr == "TimelineAddEntries":
|
|
|
+ for entry in instruction{"entries"}:
|
|
|
+ let content = entry{"content"}
|
|
|
+ if content{"entryType"}.getStr == "TimelineTimelineItem":
|
|
|
+ with legacy, content{"itemContent", "user_results", "result", "legacy"}:
|
|
|
+ result.content.add parseUser(legacy)
|
|
|
+ elif content{"cursorType"}.getStr == "Bottom":
|
|
|
+ result.bottom = content{"value"}.getStr
|
|
|
|
|
|
- for u in js{"users"}:
|
|
|
- result.content.add parseProfile(u)
|
|
|
|
|
|
proc parsePoll(js: JsonNode): Poll =
|
|
|
let vals = js{"binding_values"}
|
|
|
@@ -206,7 +225,7 @@ proc parseTweet(js: JsonNode): Tweet =
|
|
|
time: js{"created_at"}.getTime,
|
|
|
hasThread: js{"self_thread"}.notNull,
|
|
|
available: true,
|
|
|
- profile: Profile(id: js{"user_id_str"}.getStr),
|
|
|
+ user: User(id: js{"user_id_str"}.getStr),
|
|
|
stats: TweetStats(
|
|
|
replies: js{"reply_count"}.getInt,
|
|
|
retweets: js{"retweet_count"}.getInt,
|
|
|
@@ -244,7 +263,7 @@ proc parseTweet(js: JsonNode): Tweet =
|
|
|
of "video":
|
|
|
result.video = some(parseVideo(m))
|
|
|
with user, m{"additional_media_info", "source_user"}:
|
|
|
- result.attribution = some(parseProfile(user))
|
|
|
+ result.attribution = some(parseUser(user))
|
|
|
of "animated_gif":
|
|
|
result.gif = some(parseGif(m))
|
|
|
else: discard
|
|
|
@@ -298,36 +317,32 @@ proc parseGlobalObjects(js: JsonNode): GlobalObjects =
|
|
|
users = ? js{"globalObjects", "users"}
|
|
|
|
|
|
for k, v in users:
|
|
|
- result.users[k] = parseProfile(v, k)
|
|
|
+ result.users[k] = parseUser(v, k)
|
|
|
|
|
|
for k, v in tweets:
|
|
|
var tweet = parseTweet(v)
|
|
|
- if tweet.profile.id in result.users:
|
|
|
- tweet.profile = result.users[tweet.profile.id]
|
|
|
+ if tweet.user.id in result.users:
|
|
|
+ tweet.user = result.users[tweet.user.id]
|
|
|
result.tweets[k] = tweet
|
|
|
|
|
|
proc parseThread(js: JsonNode; global: GlobalObjects): tuple[thread: Chain, self: bool] =
|
|
|
result.thread = Chain()
|
|
|
- for t in js{"content", "timelineModule", "items"}:
|
|
|
- let content = t{"item", "content"}
|
|
|
- if "Self" in content{"tweet", "displayType"}.getStr:
|
|
|
+
|
|
|
+ let thread = js{"content", "item", "content", "conversationThread"}
|
|
|
+ with cursor, thread{"showMoreCursor"}:
|
|
|
+ result.thread.cursor = cursor{"value"}.getStr
|
|
|
+ result.thread.hasMore = true
|
|
|
+
|
|
|
+ for t in thread{"conversationComponents"}:
|
|
|
+ let content = t{"conversationTweetComponent", "tweet"}
|
|
|
+
|
|
|
+ if content{"displayType"}.getStr == "SelfThread":
|
|
|
result.self = true
|
|
|
|
|
|
- let entry = t{"entryId"}.getStr
|
|
|
- if "show_more" in entry:
|
|
|
- let
|
|
|
- cursor = content{"timelineCursor"}
|
|
|
- more = cursor{"displayTreatment", "actionText"}.getStr
|
|
|
- result.thread.cursor = cursor{"value"}.getStr
|
|
|
- if more.len > 0 and more[0].isDigit():
|
|
|
- result.thread.more = parseInt(more[0 ..< more.find(" ")])
|
|
|
- else:
|
|
|
- result.thread.more = -1
|
|
|
- else:
|
|
|
- var tweet = finalizeTweet(global, t.getEntryId)
|
|
|
- if not tweet.available:
|
|
|
- tweet.tombstone = getTombstone(content{"tombstone"})
|
|
|
- result.thread.content.add tweet
|
|
|
+ var tweet = finalizeTweet(global, content{"id"}.getStr)
|
|
|
+ if not tweet.available:
|
|
|
+ tweet.tombstone = getTombstone(content{"tombstone"})
|
|
|
+ result.thread.content.add tweet
|
|
|
|
|
|
proc parseConversation*(js: JsonNode; tweetId: string): Conversation =
|
|
|
result = Conversation(replies: Result[Chain](beginning: true))
|
|
|
@@ -373,8 +388,8 @@ proc parseInstructions[T](res: var Result[T]; global: GlobalObjects; js: JsonNod
|
|
|
elif "bottom" in r{"entryId"}.getStr:
|
|
|
res.bottom = r.getCursor
|
|
|
|
|
|
-proc parseUsers*(js: JsonNode; after=""): Result[Profile] =
|
|
|
- result = Result[Profile](beginning: after.len == 0)
|
|
|
+proc parseUsers*(js: JsonNode; after=""): Result[User] =
|
|
|
+ result = Result[User](beginning: after.len == 0)
|
|
|
let global = parseGlobalObjects(? js)
|
|
|
|
|
|
let instructions = ? js{"timeline", "instructions"}
|
|
|
@@ -404,7 +419,7 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline =
|
|
|
|
|
|
for e in instructions[0]{"addEntries", "entries"}:
|
|
|
let entry = e{"entryId"}.getStr
|
|
|
- if "tweet" in entry or "sq-I-t" in entry or "tombstone" in entry:
|
|
|
+ if "tweet" in entry or entry.startsWith("sq-I-t") or "tombstone" in entry:
|
|
|
let tweet = finalizeTweet(global, e.getEntryId)
|
|
|
if not tweet.available: continue
|
|
|
result.content.add tweet
|
|
|
@@ -412,6 +427,12 @@ proc parseTimeline*(js: JsonNode; after=""): Timeline =
|
|
|
result.top = e.getCursor
|
|
|
elif "cursor-bottom" in entry:
|
|
|
result.bottom = e.getCursor
|
|
|
+ elif entry.startsWith("sq-C"):
|
|
|
+ with cursor, e{"content", "operation", "cursor"}:
|
|
|
+ if cursor{"cursorType"}.getStr == "Bottom":
|
|
|
+ result.bottom = cursor{"value"}.getStr
|
|
|
+ else:
|
|
|
+ result.top = cursor{"value"}.getStr
|
|
|
|
|
|
proc parsePhotoRail*(js: JsonNode): PhotoRail =
|
|
|
for tweet in js:
|