module Command exposing ( initCommands , navigateToAuthUrl , registerApp , saveClients , saveRegistration , loadNotifications , loadUserAccount , loadAccount , loadAccountFollowers , loadAccountFollowing , loadAccountPins , loadHomeTimeline , loadLocalTimeline , loadGlobalTimeline , loadAccountTimeline , loadAccountTimelineReplies , loadFavoriteTimeline , loadHashtagTimeline , loadMutes , loadBlocks , loadNextTimeline , loadRelationships , loadThread , loadTimelines , postStatus , updateDomStatus , deleteStatus , reblogStatus , unreblogStatus , favouriteStatus , unfavouriteStatus , follow , unfollow , mute , unmute , block , unblock , uploadMedia , focusId , scrollColumnToTop , scrollColumnToBottom , scrollToThreadStatus , searchAccounts , search , notifyStatus , notifyNotification , clearDraft ) import Dom import Dom.Scroll import Json.Encode as Encode import Json.Decode as Decode import HttpBuilder import Mastodon.ApiUrl as ApiUrl import Mastodon.Decoder exposing (..) import Mastodon.Encoder exposing (..) import Mastodon.Helper exposing (extractStatusId) import Mastodon.Http exposing (..) import Mastodon.Model exposing (..) import Navigation import Ports import String.Extra exposing (replace) import Task import Types exposing (..) import View.Formatter exposing (textContent) initCommands : Maybe AppRegistration -> Maybe Client -> Maybe String -> Cmd Msg initCommands registration client authCode = Cmd.batch <| case authCode of Just authCode -> case registration of Just registration -> [ getAccessToken registration authCode , Ports.deleteRegistration "" ] Nothing -> [] Nothing -> [ loadUserAccount client, loadTimelines client ] getAccessToken : AppRegistration -> String -> Cmd Msg getAccessToken registration authCode = HttpBuilder.post (registration.server ++ ApiUrl.oauthToken) |> HttpBuilder.withJsonBody (authorizationCodeEncoder registration authCode) |> withBodyDecoder (accessTokenDecoder registration) |> send (MastodonEvent << AccessToken) navigateToAuthUrl : AppRegistration -> Cmd Msg navigateToAuthUrl registration = Navigation.load <| getAuthorizationUrl registration registerApp : Model -> Cmd Msg registerApp { server, location } = let redirectUri = location.origin ++ location.pathname cleanServer = if String.endsWith "/" server then String.dropRight 1 server else server clientName = "tooty" scope = "read write follow" website = "https://git.sdf.org/flewkey/tooty" in HttpBuilder.post (cleanServer ++ ApiUrl.apps) |> withBodyDecoder (appRegistrationDecoder cleanServer scope) |> HttpBuilder.withJsonBody (appRegistrationEncoder clientName redirectUri scope website) |> send (MastodonEvent << AppRegistered) saveClients : List Client -> Cmd Msg saveClients clients = clients |> List.map clientEncoder |> Encode.list |> Encode.encode 0 |> Ports.saveClients saveRegistration : AppRegistration -> Cmd Msg saveRegistration registration = registrationEncoder registration |> Encode.encode 0 |> Ports.saveRegistration loadNotifications : Maybe Client -> Maybe String -> Cmd Msg loadNotifications client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.notifications url) |> withClient client |> withBodyDecoder (Decode.list notificationDecoder) |> send (MastodonEvent << Notifications (url /= Nothing)) Nothing -> Cmd.none loadUserAccount : Maybe Client -> Cmd Msg loadUserAccount client = case client of Just client -> HttpBuilder.get ApiUrl.userAccount |> withClient client |> withBodyDecoder accountDecoder |> send (MastodonEvent << CurrentUser) Nothing -> Cmd.none loadAccount : Maybe Client -> String -> Cmd Msg loadAccount client accountId = case client of Just client -> Cmd.batch [ HttpBuilder.get (ApiUrl.account accountId) |> withClient client |> withBodyDecoder accountDecoder |> send (MastodonEvent << AccountReceived) , requestRelationships client [ accountId ] |> send (MastodonEvent << AccountRelationship) ] Nothing -> Cmd.none loadAccountPins : Maybe Client -> String -> Maybe String -> Cmd Msg loadAccountPins client accountId url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.accountTimeline accountId) url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> withQueryParams [ ( "pinned", "true" ) ] |> send (MastodonEvent << AccountPins (url /= Nothing)) Nothing -> Cmd.none loadAccountFollowers : Maybe Client -> String -> Maybe String -> Cmd Msg loadAccountFollowers client accountId url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.followers accountId) url) |> withClient client |> withBodyDecoder (Decode.list accountDecoder) |> send (MastodonEvent << AccountFollowers (url /= Nothing)) Nothing -> Cmd.none loadAccountFollowing : Maybe Client -> String -> Maybe String -> Cmd Msg loadAccountFollowing client accountId url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.following accountId) url) |> withClient client |> withBodyDecoder (Decode.list accountDecoder) |> send (MastodonEvent << AccountFollowing (url /= Nothing)) Nothing -> Cmd.none search : Maybe Client -> String -> Cmd Msg search client term = case client of Just client -> let cleanTerm = term |> replace "#" "" in HttpBuilder.get ApiUrl.search |> withClient client |> withBodyDecoder searchResultsDecoder |> withQueryParams [ ( "q", cleanTerm ), ( "resolve", "true" ) ] |> send (MastodonEvent << SearchResultsReceived) Nothing -> Cmd.none searchAccounts : Maybe Client -> String -> Int -> Bool -> Cmd Msg searchAccounts client query limit resolve = if query == "" then Cmd.none else case client of Just client -> let qs = [ ( "q", "@"++query ) , ( "resolve" , if resolve then "true" else "false" ) , ( "exclude_unreviewed", "false" ) ] in HttpBuilder.get ApiUrl.search |> withClient client |> withBodyDecoder (searchResultsDecoder) |> withQueryParams qs |> send (MastodonEvent << AutoSearch) Nothing -> Cmd.none requestRelationships : Client -> List String -> Request (List Relationship) requestRelationships client ids = HttpBuilder.get ApiUrl.relationships |> withClient client |> withBodyDecoder (Decode.list relationshipDecoder) |> withQueryParams (List.map (\id -> ( "id[]", id )) ids) loadRelationships : Maybe Client -> List String -> Cmd Msg loadRelationships client ids = if List.length ids > 0 then case client of Just client -> requestRelationships client ids |> send (MastodonEvent << AccountRelationships) Nothing -> Cmd.none else Cmd.none loadThread : Maybe Client -> StatusId -> Cmd Msg loadThread client id = case client of Just client -> Cmd.batch [ HttpBuilder.get (ApiUrl.status id) |> withClient client |> withBodyDecoder statusDecoder |> send (MastodonEvent << (ThreadStatusLoaded id)) , HttpBuilder.get (ApiUrl.context id) |> withClient client |> withBodyDecoder contextDecoder |> send (MastodonEvent << (ThreadContextLoaded id)) ] Nothing -> Cmd.none loadHomeTimeline : Maybe Client -> Maybe String -> Cmd Msg loadHomeTimeline client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.homeTimeline url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> send (MastodonEvent << HomeTimeline (url /= Nothing)) Nothing -> Cmd.none loadLocalTimeline : Maybe Client -> Maybe String -> Cmd Msg loadLocalTimeline client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.publicTimeline url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> send (MastodonEvent << LocalTimeline (url /= Nothing)) Nothing -> Cmd.none loadGlobalTimeline : Maybe Client -> Maybe String -> Cmd Msg loadGlobalTimeline client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.publicTimeline url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> send (MastodonEvent << GlobalTimeline (url /= Nothing)) Nothing -> Cmd.none loadAccountTimeline : Maybe Client -> String -> Maybe String -> Cmd Msg loadAccountTimeline client accountId url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.accountTimeline accountId) url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> withQueryParams [ ( "exclude_replies", "true" ) ] |> send (MastodonEvent << AccountTimeline (url /= Nothing)) Nothing -> Cmd.none loadAccountTimelineReplies : Maybe Client -> String -> Maybe String -> Cmd Msg loadAccountTimelineReplies client accountId url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.accountTimeline accountId) url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> withQueryParams [ ( "exclude_replies", "false" ) ] |> send (MastodonEvent << AccountTimeline (url /= Nothing)) Nothing -> Cmd.none loadFavoriteTimeline : Maybe Client -> Maybe String -> Cmd Msg loadFavoriteTimeline client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.favouriteTimeline url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> send (MastodonEvent << FavoriteTimeline (url /= Nothing)) Nothing -> Cmd.none loadHashtagTimeline : Maybe Client -> String -> Maybe String -> Cmd Msg loadHashtagTimeline client hashtag url = case client of Just client -> HttpBuilder.get (Maybe.withDefault (ApiUrl.hashtag hashtag) url) |> withClient client |> withBodyDecoder (Decode.list statusDecoder) |> send (MastodonEvent << HashtagTimeline (url /= Nothing)) Nothing -> Cmd.none loadMutes : Maybe Client -> Maybe String -> Cmd Msg loadMutes client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.mutes url) |> withClient client |> withBodyDecoder (Decode.list accountDecoder) |> send (MastodonEvent << Mutes (url /= Nothing)) Nothing -> Cmd.none loadBlocks : Maybe Client -> Maybe String -> Cmd Msg loadBlocks client url = case client of Just client -> HttpBuilder.get (Maybe.withDefault ApiUrl.blocks url) |> withClient client |> withBodyDecoder (Decode.list accountDecoder) |> send (MastodonEvent << Blocks (url /= Nothing)) Nothing -> Cmd.none loadTimelines : Maybe Client -> Cmd Msg loadTimelines client = Cmd.batch [ loadHomeTimeline client Nothing , loadLocalTimeline client Nothing , loadGlobalTimeline client Nothing , loadNotifications client Nothing ] loadNextTimeline : Model -> String -> String -> Cmd Msg loadNextTimeline { clients, currentView, accountInfo } id next = let client = List.head clients in case id of "notifications" -> loadNotifications client (Just next) "home-timeline" -> loadHomeTimeline client (Just next) "local-timeline" -> loadLocalTimeline client (Just next) "global-timeline" -> loadGlobalTimeline client (Just next) "favorite-timeline" -> loadFavoriteTimeline client (Just next) "hashtag-timeline" -> case currentView of HashtagView hashtag -> loadHashtagTimeline client hashtag (Just next) _ -> Cmd.none "account-timeline" -> case accountInfo.account of Just account -> loadAccountTimeline client account.id (Just next) _ -> Cmd.none "account-followers" -> case accountInfo.account of Just account -> loadAccountFollowers client account.id (Just next) _ -> Cmd.none "account-following" -> case accountInfo.account of Just account -> loadAccountFollowing client account.id (Just next) _ -> Cmd.none _ -> Cmd.none postStatus : Maybe Client -> StatusRequestBody -> Cmd Msg postStatus client draft = case client of Just client -> HttpBuilder.post ApiUrl.statuses |> withClient client |> HttpBuilder.withJsonBody (statusRequestBodyEncoder draft) |> withBodyDecoder statusDecoder |> send (MastodonEvent << StatusPosted) Nothing -> Cmd.none updateDomStatus : String -> Cmd Msg updateDomStatus statusText = Ports.setStatus { id = "status", status = statusText } deleteStatus : Maybe Client -> StatusId -> Cmd Msg deleteStatus client id = case client of Just client -> HttpBuilder.delete (ApiUrl.status id) |> withClient client |> withBodyDecoder (Decode.succeed id) |> send (MastodonEvent << StatusDeleted) Nothing -> Cmd.none reblogStatus : Maybe Client -> StatusId -> Cmd Msg reblogStatus client statusId = case client of Just client -> HttpBuilder.post (ApiUrl.reblog statusId) |> withClient client |> withBodyDecoder statusDecoder |> send (MastodonEvent << Reblogged) Nothing -> Cmd.none unreblogStatus : Maybe Client -> StatusId -> Cmd Msg unreblogStatus client statusId = case client of Just client -> HttpBuilder.post (ApiUrl.unreblog statusId) |> withClient client |> withBodyDecoder statusDecoder |> send (MastodonEvent << Unreblogged) Nothing -> Cmd.none favouriteStatus : Maybe Client -> StatusId -> Cmd Msg favouriteStatus client statusId = case client of Just client -> HttpBuilder.post (ApiUrl.favourite statusId) |> withClient client |> withBodyDecoder statusDecoder |> send (MastodonEvent << FavoriteAdded) Nothing -> Cmd.none unfavouriteStatus : Maybe Client -> StatusId -> Cmd Msg unfavouriteStatus client statusId = case client of Just client -> HttpBuilder.post (ApiUrl.unfavourite statusId) |> withClient client |> withBodyDecoder statusDecoder |> send (MastodonEvent << FavoriteRemoved) Nothing -> Cmd.none follow : Maybe Client -> Account -> Cmd Msg follow client account = case client of Just client -> HttpBuilder.post (ApiUrl.follow account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountFollowed account)) Nothing -> Cmd.none unfollow : Maybe Client -> Account -> Cmd Msg unfollow client account = case client of Just client -> HttpBuilder.post (ApiUrl.unfollow account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountUnfollowed account)) Nothing -> Cmd.none mute : Maybe Client -> Account -> Cmd Msg mute client account = case client of Just client -> HttpBuilder.post (ApiUrl.mute account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountMuted account)) Nothing -> Cmd.none unmute : Maybe Client -> Account -> Cmd Msg unmute client account = case client of Just client -> HttpBuilder.post (ApiUrl.unmute account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountUnmuted account)) Nothing -> Cmd.none block : Maybe Client -> Account -> Cmd Msg block client account = case client of Just client -> HttpBuilder.post (ApiUrl.block account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountBlocked account)) Nothing -> Cmd.none unblock : Maybe Client -> Account -> Cmd Msg unblock client account = case client of Just client -> HttpBuilder.post (ApiUrl.unblock account.id) |> withClient client |> withBodyDecoder relationshipDecoder |> send (MastodonEvent << (AccountUnblocked account)) Nothing -> Cmd.none uploadMedia : Maybe Client -> String -> Cmd Msg uploadMedia client fileInputId = case client of Just { server, token } -> Ports.uploadMedia { id = fileInputId , url = server ++ ApiUrl.uploadMedia , token = token } Nothing -> Cmd.none updateMediaDescription : Maybe Client -> String -> String -> Cmd Msg updateMediaDescription client mediaId description = case client of Just { server, token } -> Ports.updateMedia { id = mediaId , url = server ++ (ApiUrl.updateMedia mediaId) , token = token , description = description } Nothing -> Cmd.none focusId : String -> Cmd Msg focusId id = Dom.focus id |> Task.attempt (always NoOp) scrollColumnToTop : String -> Cmd Msg scrollColumnToTop column = Task.attempt (always NoOp) <| Dom.Scroll.toTop column scrollColumnToBottom : String -> Cmd Msg scrollColumnToBottom column = Task.attempt (always NoOp) <| Dom.Scroll.toBottom column scrollToThreadStatus : String -> Cmd Msg scrollToThreadStatus cssId = Ports.scrollIntoView <| "thread-status-" ++ cssId notifyStatus : Status -> Cmd Msg notifyStatus status = Ports.notify { title = status.account.acct , icon = status.account.avatar , body = status.content |> textContent , clickUrl = "#thread/" ++ extractStatusId status.id } notifyNotification : Notification -> Cmd Msg notifyNotification notification = case notification.status of Just status -> case notification.type_ of "reblog" -> Ports.notify { title = notification.account.acct ++ " reboosted" , icon = notification.account.avatar , body = status.content |> textContent , clickUrl = "#thread/" ++ extractStatusId status.id } "favourite" -> Ports.notify { title = notification.account.acct ++ " favorited" , icon = notification.account.avatar , body = status.content |> textContent , clickUrl = "#thread/" ++ extractStatusId status.id } "mention" -> Ports.notify { title = notification.account.acct ++ " mentioned you" , icon = notification.account.avatar , body = status.content |> textContent , clickUrl = "#thread/" ++ extractStatusId status.id } _ -> Cmd.none Nothing -> case notification.type_ of "follow" -> Ports.notify { title = notification.account.acct ++ " follows you" , icon = notification.account.avatar , body = notification.account.note , clickUrl = "#account/" ++ notification.account.id } "follow_request" -> Ports.notify { title = notification.account.acct ++ " sent you a follow request" , icon = notification.account.avatar , body = notification.account.note , clickUrl = "#account/" ++ notification.account.id } _ -> Cmd.none clearDraft : Cmd Msg clearDraft = Task.perform identity (Task.succeed (DraftEvent ClearDraft))