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