1
0
Fork 0
tooty/src/Mastodon/Decoder.elm

371 lines
12 KiB
Elm

module Mastodon.Decoder
exposing
( appRegistrationDecoder
, accessTokenDecoder
, accountDecoder
, accountMovedDecoder
, attachmentDecoder
, contextDecoder
, decodeWebSocketMessage
, decodeClients
, emojiDecoder
, fieldDecoder
, mastodonErrorDecoder
, mentionDecoder
, notificationDecoder
, tagDecoder
, quoteDecoder
, reblogDecoder
, relationshipDecoder
, hashtagHistoryDecoder
, hashtagDecoder
, searchResultsDecoder
, statusDecoder
, webSocketEventDecoder
, pleromaDecoder
, pollIdDecoder
, pollOptionDecoder
, pollDecoder
)
import Json.Decode as Decode
import Json.Decode.Pipeline as Pipe
import Mastodon.Model exposing (..)
import Mastodon.WebSocket exposing (..)
appRegistrationDecoder : String -> String -> Decode.Decoder AppRegistration
appRegistrationDecoder server scope =
Pipe.decode AppRegistration
|> Pipe.hardcoded server
|> Pipe.hardcoded scope
|> Pipe.required "client_id" Decode.string
|> Pipe.required "client_secret" Decode.string
|> Pipe.required "id" idDecoder
|> Pipe.required "redirect_uri" Decode.string
accessTokenDecoder : AppRegistration -> Decode.Decoder AccessTokenResult
accessTokenDecoder registration =
Pipe.decode AccessTokenResult
|> Pipe.hardcoded registration.server
|> Pipe.required "access_token" Decode.string
emojiDecoder : Decode.Decoder Emoji
emojiDecoder =
Pipe.decode Emoji
|> Pipe.required "shortcode" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.required "static_url" Decode.string
|> Pipe.required "visible_in_picker" Decode.bool
fieldDecoder : Decode.Decoder Field
fieldDecoder =
Pipe.decode Field
|> Pipe.optional "name" Decode.string ""
|> Pipe.optional "value" Decode.string ""
|> Pipe.optional "verified_at" Decode.string ""
sourceDecoder : Decode.Decoder Source
sourceDecoder =
Pipe.decode Source
|> Pipe.optional "privacy" Decode.string "public"
|> Pipe.optional "sensitive" Decode.bool False
|> Pipe.optional "language" Decode.string ""
|> Pipe.optional "note" Decode.string ""
|> Pipe.optional "fields" (Decode.list fieldDecoder) []
|> Pipe.optional "follow_requests_count" Decode.int 0
accountDecoder : Decode.Decoder Account
accountDecoder =
Pipe.decode Account
|> Pipe.required "acct" Decode.string
|> Pipe.required "avatar" Decode.string
|> Pipe.optional "avatar_static" Decode.string ""
|> Pipe.required "created_at" Decode.string
|> Pipe.optional "last_status_at" Decode.string ""
|> Pipe.required "display_name" Decode.string
|> Pipe.required "followers_count" Decode.int
|> Pipe.required "following_count" Decode.int
|> Pipe.required "header" Decode.string
|> Pipe.optional "header_static" Decode.string ""
|> Pipe.required "id" idDecoder
|> Pipe.optional "locked" Decode.bool False
|> Pipe.optional "bot" Decode.bool False
|> Pipe.required "note" Decode.string
|> Pipe.required "statuses_count" Decode.int
|> Pipe.required "url" Decode.string
|> Pipe.required "username" Decode.string
|> Pipe.optional "source" sourceDecoder
{ privacy = "public"
, sensitive = False
, language = ""
, note = ""
, fields = []
, follow_requests_count = 0
}
|> Pipe.optional "emojis" (Decode.list emojiDecoder) []
|> Pipe.optional "fields" (Decode.list fieldDecoder) []
|> Pipe.optional "moved" accountMovedDecoder
{ acct = ""
, avatar = ""
, display_name = ""
, header = ""
, id = ""
, locked = False
, bot = False
, username = ""
, url = ""
, note = ""
}
accountMovedDecoder : Decode.Decoder AccountMoved
accountMovedDecoder =
Pipe.decode AccountMoved
|> Pipe.required "acct" Decode.string
|> Pipe.optional "avatar" Decode.string ""
|> Pipe.optional "display_name" Decode.string ""
|> Pipe.optional "header" Decode.string ""
|> Pipe.required "id" idDecoder
|> Pipe.optional "locked" Decode.bool False
|> Pipe.optional "bot" Decode.bool False
|> Pipe.required "username" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.optional "note" Decode.string ""
applicationDecoder : Decode.Decoder Application
applicationDecoder =
Pipe.decode Application
|> Pipe.required "name" Decode.string
|> Pipe.required "website" (Decode.nullable Decode.string)
attachmentDecoder : Decode.Decoder Attachment
attachmentDecoder =
Pipe.decode Attachment
|> Pipe.required "id" idDecoder
|> Pipe.required "type" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.optional "remote_url" Decode.string ""
|> Pipe.optional "preview_url" Decode.string ""
|> Pipe.optional "text_url" (Decode.nullable Decode.string) Nothing
|> Pipe.optional "description" (Decode.nullable Decode.string) Nothing
contextDecoder : Decode.Decoder Context
contextDecoder =
Pipe.decode Context
|> Pipe.required "ancestors" (Decode.list statusDecoder)
|> Pipe.required "descendants" (Decode.list statusDecoder)
clientDecoder : Decode.Decoder Client
clientDecoder =
Pipe.decode Client
|> Pipe.required "server" Decode.string
|> Pipe.required "token" Decode.string
|> Pipe.required "account" (Decode.maybe accountDecoder)
decodeClients : String -> Result String (List Client)
decodeClients json =
Decode.decodeString (Decode.list clientDecoder) json
mastodonErrorDecoder : Decode.Decoder String
mastodonErrorDecoder =
Decode.field "error" Decode.string
mentionDecoder : Decode.Decoder Mention
mentionDecoder =
Pipe.decode Mention
|> Pipe.required "id" idDecoder
|> Pipe.required "url" Decode.string
|> Pipe.required "username" Decode.string
|> Pipe.required "acct" Decode.string
notificationDecoder : Decode.Decoder Notification
notificationDecoder =
Pipe.decode Notification
|> Pipe.required "id" idDecoder
|> Pipe.required "type" Decode.string
|> Pipe.required "created_at" Decode.string
|> Pipe.required "account" accountDecoder
|> Pipe.optional "status" (Decode.nullable statusDecoder) Nothing
relationshipDecoder : Decode.Decoder Relationship
relationshipDecoder =
Pipe.decode Relationship
|> Pipe.required "id" idDecoder
|> Pipe.required "blocking" Decode.bool
|> Pipe.required "followed_by" Decode.bool
|> Pipe.required "following" Decode.bool
|> Pipe.required "muting" Decode.bool
|> Pipe.required "requested" Decode.bool
|> Pipe.required "blocked_by" Decode.bool
tagDecoder : Decode.Decoder Tag
tagDecoder =
Pipe.decode Tag
|> Pipe.required "name" Decode.string
|> Pipe.required "url" Decode.string
quoteDecoder : Decode.Decoder Quote
quoteDecoder =
Decode.map Quote (Decode.lazy (\_ -> statusDecoder))
reblogDecoder : Decode.Decoder Reblog
reblogDecoder =
Decode.map Reblog (Decode.lazy (\_ -> statusDecoder))
hashtagHistoryDecoder : Decode.Decoder HashtagHistory
hashtagHistoryDecoder =
Pipe.decode HashtagHistory
|> Pipe.required "day" Decode.string
|> Pipe.required "uses" Decode.string
|> Pipe.required "accounts" Decode.string
hashtagDecoder : Decode.Decoder Hashtag
hashtagDecoder =
Pipe.decode Hashtag
|> Pipe.required "name" Decode.string
|> Pipe.required "url" Decode.string
|> Pipe.optional "history" (Decode.list hashtagHistoryDecoder) []
searchResultsDecoder : Decode.Decoder SearchResults
searchResultsDecoder =
Pipe.decode SearchResults
|> Pipe.required "accounts" (Decode.list accountDecoder)
|> Pipe.required "statuses" (Decode.list statusDecoder)
|> Pipe.required "hashtags" (Decode.list hashtagDecoder)
idDecoder : Decode.Decoder String
idDecoder =
-- Note: since v2.0.0 of the Mastodon API, ids are treated as strings, so we
-- treat all ids as strings.
Decode.oneOf
[ Decode.string
, Decode.int |> Decode.map toString
]
pleromaDecoder : Decode.Decoder Pleroma
pleromaDecoder =
Pipe.decode Pleroma
|> Pipe.optional "quote" (Decode.lazy (\_ -> Decode.nullable quoteDecoder)) Nothing
pollIdDecoder : Decode.Decoder PollId
pollIdDecoder =
idDecoder |> Decode.map PollId
pollOptionDecoder : Decode.Decoder PollOption
pollOptionDecoder =
Pipe.decode PollOption
|> Pipe.required "title" Decode.string
|> Pipe.required "votes_count" Decode.int
pollDecoder : Decode.Decoder Poll
pollDecoder =
Pipe.decode Poll
|> Pipe.required "id" pollIdDecoder
|> Pipe.required "expired" Decode.bool
|> Pipe.required "voted" Decode.bool
|> Pipe.required "votes_count" Decode.int
|> Pipe.required "options" (Decode.list pollOptionDecoder)
statusIdDecoder : Decode.Decoder StatusId
statusIdDecoder =
idDecoder |> Decode.map StatusId
statusDecoder : Decode.Decoder Status
statusDecoder =
Pipe.decode Status
|> Pipe.required "account" accountDecoder
|> Pipe.optional "application" (Decode.nullable applicationDecoder) Nothing
|> Pipe.required "content" Decode.string
|> Pipe.required "created_at" Decode.string
|> Pipe.optional "favourited" (Decode.nullable Decode.bool) Nothing
|> Pipe.required "favourites_count" Decode.int
|> Pipe.required "id" statusIdDecoder
|> Pipe.optional "in_reply_to_account_id" (Decode.nullable idDecoder) Nothing
|> Pipe.optional "in_reply_to_id" (Decode.nullable statusIdDecoder) Nothing
|> Pipe.required "media_attachments" (Decode.list attachmentDecoder)
|> Pipe.required "mentions" (Decode.list mentionDecoder)
|> Pipe.optional "quote" (Decode.lazy (\_ -> Decode.nullable quoteDecoder)) Nothing
|> Pipe.optional "reblog" (Decode.lazy (\_ -> Decode.nullable reblogDecoder)) Nothing
|> Pipe.optional "reblogged" (Decode.nullable Decode.bool) Nothing
|> Pipe.required "reblogs_count" Decode.int
|> Pipe.required "sensitive" (Decode.nullable Decode.bool)
|> Pipe.required "spoiler_text" Decode.string
|> Pipe.required "tags" (Decode.list tagDecoder)
|> Pipe.required "uri" Decode.string
|> Pipe.required "url" (Decode.nullable Decode.string)
|> Pipe.required "visibility" Decode.string
|> Pipe.optional "pleroma" (Decode.lazy (\_ -> Decode.nullable pleromaDecoder)) Nothing
|> Pipe.optional "poll" pollDecoder
{ id = PollId("")
, expired = False
, votes_count = -1
, voted = False
, options = []
}
|> Pipe.optional "emojis" (Decode.list emojiDecoder) []
|> Pipe.optional "pinned" Decode.bool False -- Not a real value, used to show pinned indicator
webSocketEventDecoder : Decode.Decoder WebSocketMessage
webSocketEventDecoder =
Pipe.decode WebSocketMessage
|> Pipe.required "event" Decode.string
|> Pipe.required "payload"
-- NOTE: as of the Mastodon API v2.0.0, ids may be either ints or
-- strings. If we receive an int (most likely for the delete event),
-- we cast it to a string.
(Decode.oneOf
[ Decode.string
, Decode.int |> Decode.map toString
]
)
decodeWebSocketMessage : String -> WebSocketEvent
decodeWebSocketMessage message =
case (Decode.decodeString webSocketEventDecoder message) of
Ok { event, payload } ->
case event of
"update" ->
StatusUpdateEvent (Decode.decodeString statusDecoder payload)
"delete" ->
StatusDeleteEvent (StatusId payload)
"notification" ->
NotificationEvent (Decode.decodeString notificationDecoder payload)
event ->
ErrorEvent <| "Unknown WS event " ++ event
Err error ->
ErrorEvent error