Community Notes support

This commit is contained in:
Cynthia Foxwell 2024-12-17 17:01:49 -07:00
parent f5fadca631
commit 1e9d4bae83
6 changed files with 81 additions and 0 deletions

View file

@ -466,6 +466,14 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
if result.quote.isSome:
result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"}, isLegacy))
with communityNote, js{"birdwatch_pivot"}:
let note = BirdwatchNote(
id: communityNote{"note", "rest_id"}.getId,
title: communityNote{"title"}.getStr,
)
note.expandBirdwatchEntities(communityNote{"subtitle"})
result.birdwatch = some(note)
proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] =
for t in js{"content", "items"}:
let entryId = t{"entryId"}.getStr

View file

@ -319,3 +319,28 @@ proc expandNoteTweetEntities*(tweet: Tweet; js: JsonNode) =
tweet.expandTextEntities(entities, text, textSlice)
tweet.text = tweet.text.multiReplace((unicodeOpen, xmlOpen), (unicodeClose, xmlClose))
proc expandBirdwatchEntities*(note: BirdwatchNote; js: JsonNode) =
let
entities = ? js{"entities"}
text = js{"text"}.getStr
runes = text.toRunes
var replacements = newSeq[ReplaceSlice]()
for ent in entities:
let
# twitter devs in their infinite wisdom making these entities of all others 1 indexed
slice = (ent{"fromIndex"}.getInt - 1) .. (ent{"toIndex"}.getInt - 1)
refType = ent{"ref", "type"}.getStr
case refType
of "TimelineUrl":
let
url = ent{"ref", "url"}.getStr
display = $runes[slice]
replacements.add ReplaceSlice(kind: rkUrl, slice: slice, url: url, display: display)
note.text = runes.replacedWith(replacements, 0 .. runes.len)

View file

@ -7,6 +7,7 @@
@import 'card';
@import 'poll';
@import 'quote';
@import 'community_note';
.tweet-body {
flex: 1;

View file

@ -0,0 +1,30 @@
@import '_variables';
.community-note {
margin-top: 10px;
border: solid 1px var(--dark_grey);
border-radius: 10px;
background-color: var(--bg_elements);
overflow: hidden;
pointer-events: all;
position: relative;
width: 100%;
&:hover {
border-color: var(--grey);
}
.community-note-title {
font-weight: bold;
background-color: var(--bg_overlays);
padding: 6px 8px;
margin-top: 1px;
}
.community-note-text {
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
padding: 8px;
}
}

View file

@ -198,6 +198,11 @@ type
likes*: int
quotes*: int
BirdwatchNote* = ref object
id*: int64
title*: string
text*: string
Tweet* = ref object
id*: int64
threadId*: int64
@ -223,6 +228,7 @@ type
gif*: Option[Gif]
video*: Option[Video]
photos*: seq[string]
birdwatch*: Option[BirdwatchNote]
Tweets* = seq[Tweet]

View file

@ -263,6 +263,14 @@ proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode =
if quote.photos.len > 0 or quote.video.isSome or quote.gif.isSome:
renderQuoteMedia(quote, prefs, path)
proc renderCommunityNote(note: BirdwatchNote; prefs: Prefs): VNode =
buildHtml(tdiv(class="community-note")):
tdiv(class="community-note-title"):
text note.title
tdiv(class="community-note-text", dir="auto"):
verbatim replaceUrls(note.text, prefs)
proc renderLocation*(tweet: Tweet): string =
let (place, url) = tweet.getLocation()
if place.len == 0: return
@ -343,6 +351,9 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
if tweet.quote.isSome:
renderQuote(tweet.quote.get(), prefs, path)
if mainTweet and tweet.birdwatch.isSome:
renderCommunityNote(tweet.birdwatch.get(), prefs)
if mainTweet:
p(class="tweet-published"): text &"{getTime(tweet)}"