342 lines
11 KiB
Nim
342 lines
11 KiB
Nim
# SPDX-License-Identifier: AGPL-3.0-only
|
|
import strutils, strformat, options, json, sequtils, times, math
|
|
import ".."/[types, formatters, utils]
|
|
|
|
proc formatTweetForMastoAPI*(tweet: Tweet, cfg: Config, prefs: Prefs): string =
|
|
var content = replaceUrls(tweet.text, prefs, absolute=getUrlPrefix(cfg))
|
|
|
|
if tweet.poll.isSome():
|
|
let poll = get(tweet.poll)
|
|
content &= "\n<blockquote>"
|
|
for i in 0 ..< poll.options.len:
|
|
let
|
|
leader = if poll.leader == i: " leader" else: ""
|
|
val = poll.values[i]
|
|
perc = if val > 0: val / poll.votes * 100 else: 0
|
|
percStr = (&"{perc:>3.0f}").strip(chars={'.'}) & '%'
|
|
barLen = round((perc / 100) * 32).int
|
|
bar = repeat("█", barLen)
|
|
notBar = repeat(" ", 32 - barLen)
|
|
content &= &"<b>{poll.options[i]}</b> ({insertSep($val, ',')}, {percStr})\n<code>{bar}{notBar}</code>\n"
|
|
|
|
content &= &"\n{insertSep($poll.votes, ',')} votes • {poll.status}</blockquote>"
|
|
|
|
if tweet.quote.isSome():
|
|
let
|
|
quote = get(tweet.quote)
|
|
quoteContent = replaceUrls(quote.text, prefs, absolute=getUrlPrefix(cfg))
|
|
quoteUrl = &"{getUrlPrefix(cfg)}/i/status/{quote.id}"
|
|
content &= &"\n\n<blockquote><b>↘ <a href=\"{quoteUrl}\">{quote.user.fullName} (@{quote.user.username})</a></b>\n{quoteContent}"
|
|
|
|
if quote.video.isSome() or quote.gif.isSome():
|
|
content &= "\n📹"
|
|
if quote.gif.isSome():
|
|
content &= " (GIF)"
|
|
elif quote.photos.len > 0:
|
|
content &= "\n🖼️"
|
|
if quote.photos.len > 1:
|
|
content &= &" ({quote.photos.len})"
|
|
|
|
content &= "</blockquote>"
|
|
|
|
if tweet.birdwatch.isSome():
|
|
let
|
|
note = get(tweet.birdwatch)
|
|
noteContent = replaceUrls(note.text, prefs, absolute=getUrlPrefix(cfg))
|
|
content &= &"\n\n<blockquote><b>ⓘ {note.title}</b>\n{noteContent}</blockquote>"
|
|
|
|
result = content.replace("\n", "<br>")
|
|
|
|
proc getActivityStream*(tweet: Tweet, cfg: Config, prefs: Prefs): JsonNode =
|
|
let
|
|
tweetUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.id}"
|
|
tweetContent = formatTweetForMastoAPI(tweet, cfg, prefs)
|
|
var media: seq[JsonNode] = @[]
|
|
|
|
if tweet.photos.len > 0:
|
|
for imageObj in tweet.photos:
|
|
let
|
|
image = getUrlPrefix(cfg) & getPicUrl(imageObj.url)
|
|
splitUrl = imageObj.url.split('.')
|
|
var filetype = splitUrl[^1]
|
|
if filetype == "jpg":
|
|
filetype = "jpeg"
|
|
|
|
var mediaObj = newJObject()
|
|
mediaObj["type"] = %"Image"
|
|
mediaObj["mediaType"] = %("image/" & filetype)
|
|
mediaObj["url"] = %image
|
|
mediaObj["name"] = %imageObj.description
|
|
media.add(mediaObj)
|
|
|
|
if tweet.video.isSome():
|
|
let
|
|
videoObj = get(tweet.video)
|
|
vars = videoObj.variants.filterIt(it.contentType == mp4)
|
|
var description = videoObj.title
|
|
if videoObj.description.len > 0:
|
|
description = videoObj.description
|
|
|
|
let splitUrl = videoObj.thumb.split('.')
|
|
var filetype = splitUrl[^1]
|
|
if filetype == "jpg":
|
|
filetype = "jpeg"
|
|
|
|
var url: seq[JsonNode] = @[]
|
|
|
|
var thumb = newJObject()
|
|
thumb["type"] = %"Link"
|
|
thumb["mediaType"] = %("image/" & filetype)
|
|
thumb["href"] = %(getUrlPrefix(cfg) & getPicUrl(videoObj.thumb))
|
|
url.add(thumb)
|
|
|
|
var mediaObj = newJObject()
|
|
mediaObj["type"] = %"Link"
|
|
mediaObj["mediaType"] = %"video/mp4"
|
|
mediaObj["href"] = %(vars[^1].url.replace("https://video.twimg.com", getUrlPrefix(cfg) & "/tvid").replace(".mp4", ""))
|
|
url.add(mediaObj)
|
|
|
|
var wrapper = newJObject()
|
|
wrapper["type"] = %"Video"
|
|
wrapper["name"] = %description
|
|
wrapper["url"] = %url
|
|
media.add(wrapper)
|
|
elif tweet.gif.isSome():
|
|
let
|
|
gif = get(tweet.gif)
|
|
gifUrl = https & gif.url
|
|
|
|
let splitUrl = gif.thumb.split('.')
|
|
var filetype = splitUrl[^1]
|
|
if filetype == "jpg":
|
|
filetype = "jpeg"
|
|
|
|
var url: seq[JsonNode] = @[]
|
|
|
|
var thumb = newJObject()
|
|
thumb["type"] = %"Link"
|
|
thumb["mediaType"] = %("image/" & filetype)
|
|
thumb["href"] = %(getUrlPrefix(cfg) & getPicUrl(gif.thumb))
|
|
url.add(thumb)
|
|
|
|
var mediaObj = newJObject()
|
|
mediaObj["type"] = %"Link"
|
|
mediaObj["mediaType"] = %"video/mp4"
|
|
mediaObj["href"] = %(gifUrl.replace("https://video.twimg.com", getUrlPrefix(cfg) & "/tvid").replace(".mp4", ""))
|
|
url.add(mediaObj)
|
|
|
|
var wrapper = newJObject()
|
|
wrapper["type"] = %"Video"
|
|
wrapper["name"] = newJNull()
|
|
wrapper["url"] = %url
|
|
media.add(wrapper)
|
|
|
|
var context: seq[JsonNode] = @[]
|
|
let contextUrl: JsonNode = %"https://www.w3.org/ns/activitystreams"
|
|
context.add(contextUrl)
|
|
let asProps: JsonNode = %*{
|
|
"ostatus": "http://ostatus.org#",
|
|
"atomUri": "ostatus:atomUri",
|
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
|
"conversation": "ostatus:conversation",
|
|
"sensitive": "as:sensitive",
|
|
}
|
|
context.add(asProps)
|
|
|
|
var postJson = newJObject()
|
|
postJson["@context"] = %context
|
|
postJson["id"] = %tweetUrl
|
|
postJson["type"] = %"Note"
|
|
postJson["summary"] = newJNull()
|
|
if tweet.replyId.len != 0:
|
|
let replyUrl = &"{getUrlPrefix(cfg)}/i/status/{tweet.replyId}"
|
|
postJson["inReplyTo"] = %replyUrl
|
|
postJson["inReplyToAtomUri"] = %replyUrl
|
|
else:
|
|
postJson["inReplyTo"] = newJNull()
|
|
postJson["inReplyToAtomUri"] = newJNull()
|
|
postJson["published"] = %($tweet.time)
|
|
postJson["url"] = %tweetUrl
|
|
postJson["attributedTo"] = %(&"{getUrlPrefix(cfg)}/users/{tweet.user.username}")
|
|
postJson["to"] = newJArray()
|
|
postJson["cc"] = %(@["https://www.w3.org/ns/activitystreams#Public"])
|
|
postJson["sensitive"] = %false # FIXME
|
|
postJson["atomUri"] = %tweetUrl
|
|
postJson["conversation"] = %""
|
|
postJson["content"] = %tweetContent
|
|
postJson["contentMap"] = %*{
|
|
"en": tweetContent
|
|
}
|
|
postJson["attachment"] = %media
|
|
postJson["tag"] = newJArray() # TODO: parse?
|
|
postJson["replies"] = newJObject()
|
|
|
|
result = postJson
|
|
|
|
proc getActivityStream*(user: User, cfg: Config, prefs: Prefs): JsonNode =
|
|
let userUrl = &"{getUrlPrefix(cfg)}/{user.username}"
|
|
|
|
var context: seq[JsonNode] = @[]
|
|
let contextUrl: JsonNode = %"https://www.w3.org/ns/activitystreams"
|
|
context.add(contextUrl)
|
|
let contextUrl2: JsonNode = %"https://w3id.org/security/v1"
|
|
context.add(contextUrl2)
|
|
|
|
let contextAka: JsonNode = %*{
|
|
"@id": "as:alsoKnownAs",
|
|
"@type": "@id"
|
|
}
|
|
let contextMovedTo = %*{
|
|
"@id": "as:movedTo",
|
|
"@type": "@id"
|
|
}
|
|
var asProps: JsonNode = %*{
|
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
|
"schema": "http://schema.org#",
|
|
"PropertyValue": "schema:PropertyValue",
|
|
"value": "schema:value",
|
|
}
|
|
asProps["alsoKnownAs"] = contextAka
|
|
asProps["movedTo"] = contextMovedTo
|
|
context.add(asProps)
|
|
|
|
var fields: seq[JsonNode] = @[]
|
|
|
|
if user.location.len > 0:
|
|
var location = newJObject()
|
|
location["type"] = %"PropertyValue"
|
|
location["name"] = %"Location"
|
|
location["value"] = %user.location
|
|
fields.add(location)
|
|
|
|
if user.website.len > 0:
|
|
var website = newJObject()
|
|
website["type"] = %"PropertyValue"
|
|
website["name"] = %"Website"
|
|
website["value"] = %(&"<a href=\"{user.website}\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\">{user.website}</a>")
|
|
fields.add(website)
|
|
|
|
if user.botOwner.len > 0:
|
|
var botOwner = newJObject()
|
|
botOwner["type"] = %"PropertyValue"
|
|
botOwner["name"] = %"Automated by"
|
|
botOwner["value"] = %(&"<a href=\"{getUrlPrefix(cfg)}/{user.botOwner}\" translate=\"no\">{user.botOwner}</a>")
|
|
fields.add(botOwner)
|
|
|
|
if user.pcf != "" and user.pcf != "None":
|
|
var pcf = newJObject()
|
|
pcf["type"] = %"PropertyValue"
|
|
pcf["name"] = %"PCF Label"
|
|
pcf["value"] = %user.pcf
|
|
fields.add(pcf)
|
|
|
|
if user.verifiedType != none:
|
|
var verified = newJObject()
|
|
verified["type"] = %"PropertyValue"
|
|
verified["name"] = %"Verified Type"
|
|
verified["value"] = %user.verifiedType
|
|
fields.add(verified)
|
|
|
|
var userJson = newJObject()
|
|
userJson["@context"] = %context
|
|
userJson["id"] = %userUrl
|
|
userJson["type"] = %"Person"
|
|
userJson["following"] = %(userUrl & "/following")
|
|
userJson["followers"] = %(userUrl & "/followers")
|
|
userJson["inbox"] = newJNull()
|
|
userJson["outbox"] = newJNull()
|
|
userJson["featured"] = newJNull()
|
|
userJson["featuredTags"] = newJNull()
|
|
userJson["preferredUsername"] = %user.username
|
|
userJson["name"] = %user.fullname
|
|
userJson["summary"] = %user.bio
|
|
userJson["url"] = %userUrl
|
|
userJson["manuallyApprovesFollowers"] = %user.protected
|
|
userJson["discoverable"] = %true
|
|
userJson["indexable"] = %false
|
|
userJson["published"] = %($user.joinDate)
|
|
userJson["memorial"] = %false
|
|
userJson["publicKey"] = newJNull()
|
|
userJson["tag"] = newJArray()
|
|
userJson["attachment"] = %fields
|
|
userJson["endpoints"] = newJObject()
|
|
userJson["icon"] = %*{
|
|
"type": "Image",
|
|
"mediaType": "image/jpeg",
|
|
"url": getUrlPrefix(cfg) & getPicUrl(user.userPic)
|
|
}
|
|
userJson["image"] = %*{
|
|
"type": "Image",
|
|
"mediaType": "image/jpeg",
|
|
"url": getUrlPrefix(cfg) & getPicUrl(user.banner)
|
|
}
|
|
|
|
result = userJson
|
|
|
|
proc getMastoAPIUser*(user: User, cfg: Config): JsonNode =
|
|
var fields: seq[JsonNode] = @[]
|
|
|
|
if user.location.len > 0:
|
|
var location = newJObject()
|
|
location["name"] = %"Location"
|
|
location["value"] = %user.location
|
|
location["verified_at"] = newJNull()
|
|
fields.add(location)
|
|
|
|
if user.website.len > 0:
|
|
var website = newJObject()
|
|
website["name"] = %"Website"
|
|
website["value"] = %(&"<a href=\"{user.website}\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\">{user.website}</a>")
|
|
website["verified_at"] = newJNull()
|
|
fields.add(website)
|
|
|
|
if user.botOwner.len > 0:
|
|
var botOwner = newJObject()
|
|
botOwner["name"] = %"Automated by"
|
|
botOwner["value"] = %(&"<a href=\"{getUrlPrefix(cfg)}/{user.botOwner}\" translate=\"no\">{user.botOwner}</a>")
|
|
botOwner["verified_at"] = newJNull()
|
|
fields.add(botOwner)
|
|
|
|
if user.pcf != "" and user.pcf != "None":
|
|
var pcf = newJObject()
|
|
pcf["name"] = %"PCF Label"
|
|
pcf["value"] = %user.pcf
|
|
pcf["verified_at"] = newJNull()
|
|
fields.add(pcf)
|
|
|
|
if user.verifiedType != none:
|
|
var verified = newJObject()
|
|
verified["name"] = %"Verified Type"
|
|
verified["value"] = %user.verifiedType
|
|
verified["verified_at"] = newJNull()
|
|
fields.add(verified)
|
|
|
|
var userJson = newJObject()
|
|
userJson["id"] = %user.id
|
|
userJson["username"] = %user.username
|
|
userJson["acct"] = %user.username
|
|
userJson["display_name"] = %user.fullname
|
|
userJson["locked"] = %user.protected
|
|
userJson["bot"] = %user.bot
|
|
userJson["discoverable"] = %true
|
|
userJson["indexable"] = %false
|
|
userJson["group"] = %false
|
|
userJson["created_at"] = %($user.joinDate)
|
|
userJson["note"] = %user.bio
|
|
userJson["url"] = %(&"{getUrlPrefix(cfg)}/{user.username}")
|
|
userJson["uri"] = %(&"{getUrlPrefix(cfg)}/{user.username}")
|
|
userJson["avatar"] = %(getUrlPrefix(cfg) & getPicUrl(user.userPic))
|
|
userJson["avatar_static"] = %(getUrlPrefix(cfg) & getPicUrl(user.userPic))
|
|
userJson["header"] = %(getUrlPrefix(cfg) & getPicUrl(user.banner))
|
|
userJson["header_static"] = %(getUrlPrefix(cfg) & getPicUrl(user.banner))
|
|
userJson["followers_count"] = %user.followers
|
|
userJson["following_count"] = %user.following
|
|
userJson["statuses_count"] = %user.tweets
|
|
userJson["hide_collections"] = %false
|
|
userJson["noindex"] = %false
|
|
userJson["emojis"] = %(@[])
|
|
userJson["roles"] = %(@[])
|
|
userJson["fields"] = %fields
|
|
|
|
result = userJson
|