Compare commits

...

26 Commits

Author SHA1 Message Date
Bram a8d879afbb elm-format 2024-07-24 14:05:49 +02:00
Bram 61a8e18714 Add kick + invite + ban user to Matrix.Room 2024-07-24 14:03:12 +02:00
Bram a2582f36f9 Add invite function 2024-07-22 12:58:52 +02:00
Bram 41bee45693 Add global account data functions 2024-07-21 10:38:34 +02:00
Bram 3566d3ee7a Solve elm test bugs from moving user 2024-07-19 09:02:01 +02:00
Bram 8b2db7bff6 Add setAccountData to Matrix rooms 2024-07-19 09:01:42 +02:00
Bram 5319f47145 Move user from Vault to Envelop Context 2024-07-19 08:51:19 +02:00
Bram 87ebcbcd21 Add sendStateEvent to Room API 2024-07-17 14:13:13 +02:00
Bram 0521ca2f3e Add sendMessageEvent to Room API 2024-07-16 12:41:09 +02:00
Bram fee68f7e0f Merge branch 'develop' into 4-transfer-api 2024-07-16 12:34:11 +02:00
Bram 7b615c6452 Merge branch '4-transfer-api' of github.com:noordstar/elm-matrix-sdk-beta into 4-transfer-api 2024-07-16 12:33:37 +02:00
Bram 1ed9fa7d22 Prepare develop for master
elm-test --fuzz 1000 --seed 156536263253947
2024-07-16 12:06:35 +02:00
Bram cacb876a95 Fix test errors
elm-test --fuzz 1000 --seed 156536263253947
2024-07-16 12:05:23 +02:00
Bram 20504d4a8b Remove test issues & warnings 2024-07-16 10:20:38 +02:00
Bram a401c25a47 Remove issues & warnings 2024-07-16 10:10:42 +02:00
Bram f3799add87 HOTFIX: Fix syntax error 2024-07-15 16:13:11 +02:00
BramvdnHeuvel eb8d90ab8b
Enable safe recursion in VaultUpdate type
Merge pull request #33 from noordstar/safe-recursion
2024-07-15 16:08:34 +02:00
Bram 1736679e0f elm-format 2024-07-15 16:07:28 +02:00
Bram 31817ed545 Merge develop into safe-recursion 2024-07-15 16:07:08 +02:00
BramvdnHeuvel 899088d63c
Add room navigation functions to exposed library
Merge pull request #32 from noordstar/4-lib-improvement
2024-07-15 16:01:52 +02:00
Bram 48e5eae327 Merge develop into 4-lib-improvement 2024-07-15 16:00:38 +02:00
Bram 90eb06f3a1 elm-format 2024-07-15 15:57:08 +02:00
BramvdnHeuvel a9e4a39e7f
Add /sync endpoint to Elm SDK
Merge pull request #31 from noordstar/4-transfer-api
2024-07-15 15:51:24 +02:00
Bram d7a7fa385c Enable safe recursion in VaultUpdate type 2024-07-15 15:50:32 +02:00
Bram c7204c4c41 Remove Debug.log from API definition 2024-07-13 13:39:33 +02:00
Bram 458ea59425 Add ways to navigate through rooms 2024-07-13 09:50:39 +02:00
34 changed files with 1519 additions and 192 deletions

View File

@ -3,7 +3,7 @@
"name": "noordstar/elm-matrix-sdk-beta",
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
"license": "EUPL-1.1",
"version": "3.3.1",
"version": "3.4.0",
"exposed-modules": [
"Matrix",
"Matrix.Event",

View File

@ -0,0 +1,116 @@
module Internal.Api.BanUser.Api exposing (Phantom, banUser)
{-|
# Ban user
This module helps to ban users from a room.
@docs Phantom, banUser
-}
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Envelope as E
import Internal.Values.Room as R
import Internal.Values.User as User exposing (User)
import Internal.Values.Vault as V
banUser : BanUserInput -> A.TaskChain (Phantom a) (Phantom a)
banUser =
A.startWithVersion "r0.0.0" banUserV1
|> A.sameForVersion "r0.0.1"
|> A.sameForVersion "r0.1.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.3.0"
|> A.sameForVersion "r0.4.0"
|> A.sameForVersion "r0.5.0"
|> A.sameForVersion "r0.6.0"
|> A.sameForVersion "r0.6.1"
|> A.forVersion "v1.1" banUserV2
|> A.sameForVersion "v1.2"
|> A.sameForVersion "v1.3"
|> A.sameForVersion "v1.4"
|> A.sameForVersion "v1.5"
|> A.sameForVersion "v1.6"
|> A.sameForVersion "v1.7"
|> A.sameForVersion "v1.8"
|> A.sameForVersion "v1.9"
|> A.sameForVersion "v1.10"
|> A.sameForVersion "v1.11"
|> A.versionChain
type alias Phantom a =
{ a | accessToken : (), baseUrl : (), versions : () }
type alias PhantomV1 a =
{ a | accessToken : (), baseUrl : () }
type alias BanUserInput =
{ reason : Maybe String
, roomId : String
, user : User
}
type alias BanUserInputV1 a =
{ a | reason : Maybe String, roomId : String, user : User }
type alias BanUserOutputV1 =
()
banUserV1 : BanUserInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
banUserV1 { reason, roomId, user } =
A.request
{ attributes =
[ R.accessToken
, R.bodyOpString "reason" reason
, R.bodyString "user_id" (User.toString user)
]
, coder = coderV1
, contextChange = always identity
, method = "POST"
, path = [ "_matrix", "client", "r0", "rooms", roomId, "ban" ]
, toUpdate =
\() ->
( E.More []
, []
)
}
banUserV2 : BanUserInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
banUserV2 { reason, roomId, user } =
A.request
{ attributes =
[ R.accessToken
, R.bodyOpString "reason" reason
, R.bodyString "user_id" (User.toString user)
]
, coder = coderV1
, contextChange = always identity
, method = "POST"
, path = [ "_matrix", "client", "v3", "rooms", roomId, "ban" ]
, toUpdate =
\() ->
( E.More []
, []
)
}
coderV1 : Json.Coder BanUserOutputV1
coderV1 =
Json.unit

View File

@ -13,7 +13,6 @@ This module looks for the right homeserver address.
import Internal.Api.Chain as C
import Internal.Api.Request as R
import Internal.Config.Leaks as L
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json

View File

@ -204,7 +204,7 @@ getEventCoderV1 =
[ "UnsignedData as described by the Matrix spec"
, "https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3roomsroomideventeventid"
]
, init = \a b c d -> Event.UnsignedData { age = a, prevContent = b, redactedBecause = c, transactionId = d }
, init = \a b c d -> Event.UnsignedData { age = a, membership = Nothing, prevContent = b, redactedBecause = c, transactionId = d }
}
(Json.field.optional.value
{ fieldName = "age"

View File

@ -1,4 +1,4 @@
module Internal.Api.Invite.Api exposing (InviteInput, Phantom, invite)
module Internal.Api.InviteUser.Api exposing (InviteInput, Phantom, inviteUser)
{-|
@ -14,7 +14,7 @@ room.
If the user was invited to the room, the homeserver will append a m.room.member
event to the room.
@docs InviteInput, Phantom, invite
@docs InviteInput, Phantom, inviteUser
-}
@ -31,8 +31,8 @@ import Internal.Values.Vault as V
{-| Invite a user to a room.
-}
invite : InviteInput -> A.TaskChain (Phantom ph1) (Phantom ph1)
invite =
inviteUser : InviteInput -> A.TaskChain (Phantom ph1) (Phantom ph1)
inviteUser =
A.startWithVersion "r0.0.0" inviteV1
|> A.sameForVersion "r0.0.1"
|> A.sameForVersion "r0.1.0"

View File

@ -0,0 +1,178 @@
module Internal.Api.KickUser.Api exposing (Phantom, kickUser)
{-|
# Kick user
This module helps to kick users from a room.
@docs Phantom, kickUser
-}
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Envelope as E
import Internal.Values.Room as R
import Internal.Values.User as User exposing (User)
import Internal.Values.Vault as V
kickUser : KickUserInput -> A.TaskChain (Phantom a) (Phantom a)
kickUser =
A.startWithVersion "r0.0.0" kickUserV1
|> A.sameForVersion "r0.0.1"
-- NOTE: Kicking a user was first added in r0.1.0
|> A.forVersion "r0.1.0" kickUserV2
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.3.0"
|> A.sameForVersion "r0.4.0"
|> A.sameForVersion "r0.5.0"
|> A.sameForVersion "r0.6.0"
|> A.sameForVersion "r0.6.1"
|> A.forVersion "v1.1" kickUserV3
|> A.sameForVersion "v1.2"
|> A.sameForVersion "v1.3"
|> A.sameForVersion "v1.4"
|> A.sameForVersion "v1.5"
|> A.sameForVersion "v1.6"
|> A.sameForVersion "v1.7"
|> A.sameForVersion "v1.8"
|> A.sameForVersion "v1.9"
|> A.sameForVersion "v1.10"
|> A.sameForVersion "v1.11"
|> A.versionChain
type alias Phantom a =
{ a | accessToken : (), baseUrl : (), versions : () }
type alias PhantomV1 a =
{ a | accessToken : (), baseUrl : () }
type alias KickUserInput =
{ avatarUrl : Maybe String
, displayname : Maybe String
, reason : Maybe String
, roomId : String
, user : User
}
type alias KickUserInputV1 a =
{ a
| avatarUrl : Maybe String
, displayname : Maybe String
, reason : Maybe String
, roomId : String
, user : User
}
type alias KickUserInputV2 a =
{ a | reason : Maybe String, roomId : String, user : User }
type alias KickUserOutputV1 =
{ eventId : Maybe String }
type alias KickUserOutputV2 =
()
kickUserV1 : KickUserInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
kickUserV1 { avatarUrl, displayname, reason, roomId, user } =
A.request
{ attributes =
[ R.accessToken
, R.bodyString "membership" "kick"
, R.bodyOpString "avatar_url" avatarUrl
, R.bodyOpString "displayname" displayname
, R.bodyOpString "reason" reason
]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "r0", "rooms", roomId, "state", "m.room.member", User.toString user ]
, toUpdate =
\out ->
( E.More []
, [ "The kick API endpoint does not exist before spec version r0.1.0 - falling back to sending state event directly."
|> log.debug
, out.eventId
|> Text.logs.sendEvent
|> log.debug
]
)
}
kickUserV2 : KickUserInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
kickUserV2 { reason, roomId, user } =
A.request
{ attributes =
[ R.accessToken
, R.bodyOpString "reason" reason
, R.bodyString "user_id" (User.toString user)
]
, coder = coderV2
, contextChange = always identity
, method = "POST"
, path = [ "_matrix", "client", "r0", "rooms", roomId, "kick" ]
, toUpdate =
\() ->
( E.More []
, []
)
}
kickUserV3 : KickUserInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
kickUserV3 { reason, roomId, user } =
A.request
{ attributes =
[ R.accessToken
, R.bodyOpString "reason" reason
, R.bodyString "user_id" (User.toString user)
]
, coder = coderV2
, contextChange = always identity
, method = "POST"
, path = [ "_matrix", "client", "v3", "rooms", roomId, "kick" ]
, toUpdate =
\() ->
( E.More []
, []
)
}
coderV1 : Json.Coder KickUserOutputV1
coderV1 =
Json.object1
{ name = "EventResponse"
, description =
[ "This object is returned after a state event has been sent."
]
, init = KickUserOutputV1
}
(Json.field.optional.value
{ fieldName = "event_id"
, toField = .eventId
, description = [ "A unique identifier for the event." ]
, coder = Json.string
}
)
coderV2 : Json.Coder KickUserOutputV2
coderV2 =
Json.unit

View File

@ -13,7 +13,6 @@ This module allows the user to log in using a username and password.
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Leaks as L
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
@ -192,7 +191,7 @@ loginWithUsernameAndPasswordV1 { username, password } =
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
]
, Text.logs.loggedInAs username
@ -234,7 +233,7 @@ loginWithUsernameAndPasswordV2 { deviceId, initialDeviceDisplayName, username, p
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
, out.deviceId
|> Maybe.map E.SetDeviceId
@ -286,7 +285,7 @@ loginWithUsernameAndPasswordV3 { deviceId, initialDeviceDisplayName, username, p
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
, out.deviceId
|> Maybe.map E.SetDeviceId
@ -338,7 +337,7 @@ loginWithUsernameAndPasswordV4 { deviceId, initialDeviceDisplayName, username, p
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
, out.wellKnown
|> Maybe.map (.homeserver >> .baseUrl)
@ -394,7 +393,7 @@ loginWithUsernameAndPasswordV5 { deviceId, initialDeviceDisplayName, username, p
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
, out.wellKnown
|> Maybe.map (.homeserver >> .baseUrl)
@ -451,7 +450,7 @@ loginWithUsernameAndPasswordV6 { deviceId, enableRefreshToken, initialDeviceDisp
}
, E.RemovePasswordIfNecessary
, out.user
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|> Maybe.map E.SetUser
|> E.Optional
, out.wellKnown
|> Maybe.map (.homeserver >> .baseUrl)
@ -507,7 +506,7 @@ loginWithUsernameAndPasswordV7 { deviceId, enableRefreshToken, initialDeviceDisp
, value = out.accessToken
}
, E.RemovePasswordIfNecessary
, E.ContentUpdate (V.SetUser out.user)
, E.SetUser out.user
, out.wellKnown
|> Maybe.map (.homeserver >> .baseUrl)
|> Maybe.map E.SetBaseUrl

View File

@ -1,6 +1,6 @@
module Internal.Api.Main exposing
( Msg
, sendMessageEvent, sync
, banUser, inviteUser, kickUser, sendMessageEvent, sendStateEvent, setAccountData, setRoomAccountData, sync
)
{-|
@ -18,7 +18,7 @@ This module is used as reference for getting
## Actions
@docs sendMessageEvent, sync
@docs banUser, inviteUser, kickUser, sendMessageEvent, sendStateEvent, setAccountData, setRoomAccountData, sync
-}
@ -26,6 +26,8 @@ import Internal.Api.Task as ITask exposing (Backpack)
import Internal.Tools.Json as Json
import Internal.Values.Context as Context
import Internal.Values.Envelope as E
import Internal.Values.User as User exposing (User)
import Internal.Values.Vault as V
{-| Update message type that is being returned.
@ -34,6 +36,77 @@ type alias Msg =
Backpack
{-| Ban a user from a room.
-}
banUser :
E.Envelope a
->
{ reason : Maybe String
, roomId : String
, toMsg : Msg -> msg
, user : User
}
-> Cmd msg
banUser env data =
ITask.run
data.toMsg
(ITask.banUser
{ reason = data.reason
, roomId = data.roomId
, user = data.user
}
)
(Context.apiFormat env.context)
{-| Invite a user to a room.
-}
inviteUser :
E.Envelope a
->
{ reason : Maybe String
, roomId : String
, toMsg : Msg -> msg
, user : User
}
-> Cmd msg
inviteUser env data =
ITask.run
data.toMsg
(ITask.inviteUser
{ reason = data.reason
, roomId = data.roomId
, user = data.user
}
)
(Context.apiFormat env.context)
{-| Kick a user from a room.
-}
kickUser :
E.Envelope a
->
{ reason : Maybe String
, roomId : String
, toMsg : Msg -> msg
, user : User
}
-> Cmd msg
kickUser env data =
ITask.run
data.toMsg
(ITask.kickUser
{ avatarUrl = Nothing
, displayname = Nothing
, reason = data.reason
, roomId = data.roomId
, user = data.user
}
)
(Context.apiFormat env.context)
{-| Send a message event.
-}
sendMessageEvent :
@ -59,6 +132,91 @@ sendMessageEvent env data =
(Context.apiFormat env.context)
{-| Send a state event to a room.
-}
sendStateEvent :
E.Envelope a
->
{ content : Json.Value
, eventType : String
, roomId : String
, stateKey : String
, toMsg : Msg -> msg
}
-> Cmd msg
sendStateEvent env data =
ITask.run
data.toMsg
(ITask.sendStateEvent
{ content = data.content
, eventType = data.eventType
, roomId = data.roomId
, stateKey = data.stateKey
}
)
(Context.apiFormat env.context)
{-| Set global account data.
-}
setAccountData :
E.Envelope a
->
{ content : Json.Value
, eventType : String
, toMsg : Msg -> msg
}
-> Cmd msg
setAccountData env data =
case env.context.user of
Just u ->
ITask.run
data.toMsg
(ITask.setAccountData
{ content = data.content
, eventType = data.eventType
, userId = User.toString u
}
)
(Context.apiFormat env.context)
Nothing ->
Cmd.none
{-| Set the account data for a Matrix room.
-}
setRoomAccountData :
E.Envelope a
->
{ content : Json.Value
, eventType : String
, roomId : String
, toMsg : Msg -> msg
}
-> Cmd msg
setRoomAccountData env data =
case env.context.user of
Just u ->
ITask.run
data.toMsg
(ITask.setRoomAccountData
{ content = data.content
, eventType = data.eventType
, roomId = data.roomId
, userId = User.toString u
}
)
(Context.apiFormat env.context)
Nothing ->
Cmd.none
-- TODO: Return error about lacking user capabilities
{-| Sync with the Matrix API to stay up-to-date.
-}
sync :

View File

@ -13,7 +13,6 @@ This module helps send message events to rooms on the Matrix API.
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Leaks as L
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json

View File

@ -0,0 +1,176 @@
module Internal.Api.SendStateEvent.Api exposing (..)
{-|
# Send state event
This module sends state events to Matrix rooms.
@docs Phantom, sendStateEvent
-}
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Envelope as E
{-| Send a state event to a Matrix room.
-}
sendStateEvent : SendStateEventInput -> A.TaskChain (Phantom a) (Phantom a)
sendStateEvent =
A.startWithVersion "r0.0.0" sendStateEventV1
|> A.sameForVersion "r0.0.1"
|> A.sameForVersion "r0.1.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.3.0"
|> A.sameForVersion "r0.4.0"
|> A.sameForVersion "r0.5.0"
|> A.sameForVersion "r0.6.0"
|> A.forVersion "r0.6.1" sendStateEventV2
|> A.forVersion "v1.1" sendStateEventV3
|> A.sameForVersion "v1.2"
|> A.sameForVersion "v1.3"
|> A.sameForVersion "v1.4"
|> A.sameForVersion "v1.5"
|> A.sameForVersion "v1.6"
|> A.sameForVersion "v1.7"
|> A.sameForVersion "v1.8"
|> A.sameForVersion "v1.9"
|> A.sameForVersion "v1.10"
|> A.sameForVersion "v1.11"
|> A.versionChain
{-| Context needed for sending a state event
-}
type alias Phantom a =
{ a | accessToken : (), baseUrl : (), versions : () }
type alias PhantomV1 a =
{ a | accessToken : (), baseUrl : () }
type alias SendStateEventInput =
{ content : Json.Value
, eventType : String
, roomId : String
, stateKey : String
}
type alias SendStateEventInputV1 a =
{ a
| content : Json.Value
, eventType : String
, roomId : String
, stateKey : String
}
type alias SendStateEventOutputV1 =
{ eventId : Maybe String }
type alias SendStateEventOutputV2 =
{ eventId : String }
sendStateEventV1 : SendStateEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
sendStateEventV1 { content, eventType, roomId, stateKey } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "r0", "rooms", roomId, "state", eventType, stateKey ]
, toUpdate =
\out ->
( E.More []
, out.eventId
|> Text.logs.sendEvent
|> log.debug
|> List.singleton
)
}
sendStateEventV2 : SendStateEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
sendStateEventV2 { content, eventType, roomId, stateKey } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV2
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "r0", "rooms", roomId, "state", eventType, stateKey ]
, toUpdate =
\out ->
( E.More []
, out.eventId
|> Maybe.Just
|> Text.logs.sendEvent
|> log.debug
|> List.singleton
)
}
sendStateEventV3 : SendStateEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
sendStateEventV3 { content, eventType, roomId, stateKey } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV2
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "v3", "rooms", roomId, "state", eventType, stateKey ]
, toUpdate =
\out ->
( E.More []
, out.eventId
|> Maybe.Just
|> Text.logs.sendEvent
|> log.debug
|> List.singleton
)
}
coderV1 : Json.Coder SendStateEventOutputV1
coderV1 =
Json.object1
{ name = "EventResponse"
, description =
[ "This object is returned after a state event has been sent."
]
, init = SendStateEventOutputV1
}
(Json.field.optional.value
{ fieldName = "event_id"
, toField = .eventId
, description = [ "A unique identifier for the event." ]
, coder = Json.string
}
)
coderV2 : Json.Coder SendStateEventOutputV2
coderV2 =
Json.object1
{ name = "EventResponse"
, description =
[ "This object is returned after a state event has been sent."
]
, init = SendStateEventOutputV2
}
(Json.field.required
{ fieldName = "event_id"
, toField = .eventId
, description = [ "A unique identifier for the event." ]
, coder = Json.string
}
)

View File

@ -0,0 +1,107 @@
module Internal.Api.SetAccountData.Api exposing (Phantom, setAccountData)
{-|
# Set Account Data
This module allows the developer to set global account data.
@docs Phantom, setAccountData
-}
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Envelope as E
import Internal.Values.Room as R
import Internal.Values.Vault as V
setAccountData : SetAccountDataInput -> A.TaskChain (Phantom a) (Phantom a)
setAccountData =
A.startWithVersion "r0.0.0" setAccountDataV1
|> A.sameForVersion "r0.0.1"
|> A.sameForVersion "r0.1.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.3.0"
|> A.sameForVersion "r0.4.0"
|> A.sameForVersion "r0.5.0"
|> A.sameForVersion "r0.6.0"
|> A.sameForVersion "r0.6.1"
|> A.forVersion "v1.1" setAccountDataV2
|> A.sameForVersion "v1.2"
|> A.sameForVersion "v1.3"
|> A.sameForVersion "v1.4"
|> A.sameForVersion "v1.5"
|> A.sameForVersion "v1.6"
|> A.sameForVersion "v1.7"
|> A.sameForVersion "v1.8"
|> A.sameForVersion "v1.9"
|> A.sameForVersion "v1.10"
|> A.sameForVersion "v1.11"
|> A.versionChain
{-| Context needed for setting global account data.
-}
type alias Phantom a =
{ a | accessToken : (), baseUrl : (), versions : () }
type alias PhantomV1 a =
{ a | accessToken : (), baseUrl : () }
type alias SetAccountDataInput =
{ content : Json.Value, eventType : String, userId : String }
type alias SetAccountDataInputV1 a =
{ a | content : Json.Value, eventType : String, userId : String }
type alias SetAccountDataOutput =
()
setAccountDataV1 : SetAccountDataInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
setAccountDataV1 { content, eventType, userId } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "r0", "user", userId, "account_data", eventType ]
, toUpdate =
\() ->
( V.SetAccountData eventType content
|> E.ContentUpdate
, []
)
}
setAccountDataV2 : SetAccountDataInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
setAccountDataV2 { content, eventType, userId } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "v3", "user", userId, "account_data", eventType ]
, toUpdate =
\() ->
( V.SetAccountData eventType content
|> E.ContentUpdate
, []
)
}
coderV1 : Json.Coder SetAccountDataOutput
coderV1 =
Json.unit

View File

@ -0,0 +1,111 @@
module Internal.Api.SetRoomAccountData.Api exposing (..)
{-|
# Set Room Account Data
This module allows the developer to set account data to a Matrix room.
@docs Phantom, setRoomAccountData
-}
import Internal.Api.Api as A
import Internal.Api.Request as R
import Internal.Config.Log exposing (log)
import Internal.Config.Text as Text
import Internal.Tools.Json as Json
import Internal.Values.Envelope as E
import Internal.Values.Room as R
import Internal.Values.Vault as V
{-| Set account data to a Matrix room.
-}
setRoomAccountData : SetRoomAccountDataInput -> A.TaskChain (Phantom a) (Phantom a)
setRoomAccountData =
A.startWithVersion "r0.0.0" setRoomAccountDataV1
|> A.sameForVersion "r0.0.1"
|> A.sameForVersion "r0.1.0"
|> A.sameForVersion "r0.2.0"
|> A.sameForVersion "r0.3.0"
|> A.sameForVersion "r0.4.0"
|> A.sameForVersion "r0.5.0"
|> A.sameForVersion "r0.6.0"
|> A.sameForVersion "r0.6.1"
|> A.forVersion "v1.1" setRoomAccountDataV2
|> A.sameForVersion "v1.2"
|> A.sameForVersion "v1.3"
|> A.sameForVersion "v1.4"
|> A.sameForVersion "v1.5"
|> A.sameForVersion "v1.6"
|> A.sameForVersion "v1.7"
|> A.sameForVersion "v1.8"
|> A.sameForVersion "v1.9"
|> A.sameForVersion "v1.10"
|> A.sameForVersion "v1.11"
|> A.versionChain
{-| Context needed for setting account data on a room.
-}
type alias Phantom a =
{ a | accessToken : (), baseUrl : (), versions : () }
type alias PhantomV1 a =
{ a | accessToken : (), baseUrl : () }
type alias SetRoomAccountDataInput =
{ content : Json.Value, eventType : String, roomId : String, userId : String }
type alias SetRoomAccountDataInputV1 a =
{ a | content : Json.Value, eventType : String, roomId : String, userId : String }
type alias SetRoomAccountDataOutputV1 =
()
setRoomAccountDataV1 : SetRoomAccountDataInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
setRoomAccountDataV1 { content, eventType, roomId, userId } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "r0", "user", userId, "rooms", roomId, "account_data", eventType ]
, toUpdate =
\() ->
( R.SetAccountData eventType content
|> V.MapRoom roomId
|> E.ContentUpdate
, []
)
}
setRoomAccountDataV2 : SetRoomAccountDataInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
setRoomAccountDataV2 { content, eventType, roomId, userId } =
A.request
{ attributes = [ R.accessToken, R.fullBody content ]
, coder = coderV1
, contextChange = always identity
, method = "PUT"
, path = [ "_matrix", "client", "v3", "user", userId, "rooms", roomId, "account_data", eventType ]
, toUpdate =
\() ->
( R.SetAccountData eventType content
|> V.MapRoom roomId
|> E.ContentUpdate
, []
)
}
coderV1 : Json.Coder SetRoomAccountDataOutputV1
coderV1 =
Json.unit

View File

@ -106,7 +106,7 @@ syncV1 data =
, method = "GET"
, path = [ "_matrix", "client", "v3", "sync" ]
, toUpdate =
Debug.log "Handling output v1" >> V1.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
V1.updateSyncResponse { filter = Filter.pass, since = data.since }
}
@ -128,7 +128,7 @@ syncV2 data =
, method = "GET"
, path = [ "_matrix", "client", "v3", "sync" ]
, toUpdate =
Debug.log "Handling output v2" >> V2.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
V2.updateSyncResponse { filter = Filter.pass, since = data.since }
}
@ -150,7 +150,7 @@ syncV3 data =
, method = "GET"
, path = [ "_matrix", "client", "v3", "sync" ]
, toUpdate =
Debug.log "Handling output v3" >> V3.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
V3.updateSyncResponse { filter = Filter.pass, since = data.since }
}
@ -172,5 +172,5 @@ syncV4 data =
, method = "GET"
, path = [ "_matrix", "client", "v3", "sync" ]
, toUpdate =
Debug.log "Handling output v4" >> V4.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
V4.updateSyncResponse { filter = Filter.pass, since = data.since }
}

View File

@ -12,7 +12,6 @@ This API module represents the /sync endpoint on Matrix spec version v1.11.
-}
import FastDict as Dict exposing (Dict)
import Internal.Api.Sync.V3 as PV
import Internal.Config.Log exposing (Log, log)
import Internal.Config.Text as Text
import Internal.Filter.Timeline exposing (Filter)

View File

@ -1,6 +1,6 @@
module Internal.Api.Task exposing
( Task, run, Backpack
, sendMessageEvent, sync
, banUser, inviteUser, kickUser, sendMessageEvent, sendStateEvent, setAccountData, setRoomAccountData, sync
)
{-|
@ -23,16 +23,22 @@ up-to-date.
## Tasks
@docs sendMessageEvent, sync
@docs banUser, inviteUser, kickUser, sendMessageEvent, sendStateEvent, setAccountData, setRoomAccountData, sync
-}
import Internal.Api.BanUser.Api
import Internal.Api.BaseUrl.Api
import Internal.Api.Chain as C
import Internal.Api.InviteUser.Api
import Internal.Api.KickUser.Api
import Internal.Api.LoginWithUsernameAndPassword.Api
import Internal.Api.Now.Api
import Internal.Api.Request as Request
import Internal.Api.SendMessageEvent.Api
import Internal.Api.SendStateEvent.Api
import Internal.Api.SetAccountData.Api
import Internal.Api.SetRoomAccountData.Api
import Internal.Api.Sync.Api
import Internal.Api.Versions.Api
import Internal.Config.Log exposing (Log, log)
@ -41,6 +47,7 @@ import Internal.Tools.Json as Json
import Internal.Values.Context as Context exposing (APIContext)
import Internal.Values.Envelope as E exposing (EnvelopeUpdate(..))
import Internal.Values.Room exposing (RoomUpdate(..))
import Internal.Values.User exposing (User)
import Internal.Values.Vault exposing (VaultUpdate(..))
import Task
@ -65,6 +72,15 @@ type alias UFTask a b =
C.TaskChain Request.Error (EnvelopeUpdate VaultUpdate) a b
{-| Ban a user from a room.
-}
banUser : { reason : Maybe String, roomId : String, user : User } -> Task
banUser input =
makeVBA
|> C.andThen (Internal.Api.BanUser.Api.banUser input)
|> finishTask
{-| Get an access token to talk to the Matrix API
-}
getAccessToken : UFTask { a | baseUrl : (), now : (), versions : () } { a | accessToken : (), baseUrl : (), now : (), versions : () }
@ -204,6 +220,31 @@ finishTask uftask =
)
{-| Invite a user to a room.
-}
inviteUser : { reason : Maybe String, roomId : String, user : User } -> Task
inviteUser input =
makeVBA
|> C.andThen (Internal.Api.InviteUser.Api.inviteUser input)
|> finishTask
{-| Kick a user from a room.
-}
kickUser :
{ avatarUrl : Maybe String
, displayname : Maybe String
, reason : Maybe String
, roomId : String
, user : User
}
-> Task
kickUser input =
makeVBA
|> C.andThen (Internal.Api.KickUser.Api.kickUser input)
|> finishTask
{-| Establish a Task Chain context where the base URL and supported list of
versions are known.
-}
@ -232,6 +273,33 @@ sendMessageEvent input =
|> finishTask
{-| Send a state event to a room.
-}
sendStateEvent : { content : Json.Value, eventType : String, roomId : String, stateKey : String } -> Task
sendStateEvent input =
makeVBA
|> C.andThen (Internal.Api.SendStateEvent.Api.sendStateEvent input)
|> finishTask
{-| Set global account data.
-}
setAccountData : { content : Json.Value, eventType : String, userId : String } -> Task
setAccountData input =
makeVBA
|> C.andThen (Internal.Api.SetAccountData.Api.setAccountData input)
|> finishTask
{-| Set account data for a Matrix room.
-}
setRoomAccountData : { content : Json.Value, eventType : String, roomId : String, userId : String } -> Task
setRoomAccountData input =
makeVBA
|> C.andThen (Internal.Api.SetRoomAccountData.Api.setRoomAccountData input)
|> finishTask
{-| Sync with the Matrix API to stay up-to-date.
-}
sync : { fullState : Maybe Bool, presence : Maybe String, since : Maybe String, timeout : Maybe Int } -> Task

View File

@ -29,7 +29,7 @@ will assume until overriden by the user.
-}
currentVersion : String
currentVersion =
"beta 3.3.1"
"beta 3.4.0"
{-| The default device name that is being communicated with the Matrix API.

View File

@ -278,6 +278,7 @@ fields :
, serverName : Desc
, suggestedAccessToken : Desc
, transaction : Desc
, user : Desc
, versions : Desc
}
, envelope :
@ -409,6 +410,9 @@ fields =
, transaction =
[ "A unique identifier for a transaction initiated by the user."
]
, user =
[ "The Matrix user the Vault is representing."
]
, versions =
[ "The versions of the Matrix protocol that are supported by the server."
]

View File

@ -189,21 +189,20 @@ ipv6RightParser n =
|. P.symbol ":"
{-| Convert an IPv6 address to a readable string format
-}
ipv6ToString : IPv6Address -> String
ipv6ToString { front, back } =
(if List.length front == 8 then
front
else if List.length back == 8 then
back
else
List.concat [ front, [ "" ], back ]
)
|> List.intersperse ":"
|> String.concat
-- {-| Convert an IPv6 address to a readable string format
-- -}
-- ipv6ToString : IPv6Address -> String
-- ipv6ToString { front, back } =
-- (if List.length front == 8 then
-- front
-- else if List.length back == 8 then
-- back
-- else
-- List.concat [ front, [ "" ], back ]
-- )
-- |> List.intersperse ":"
-- |> String.concat
portParser : Parser Int

View File

@ -1,6 +1,6 @@
module Internal.Tools.DecodeExtra exposing
( opField, opFieldWithDefault
, map9, map10, map11, map12
, map9, map10, map11, map12, map13
)
{-|
@ -18,7 +18,7 @@ This module contains helper functions that help decode JSON.
## Extended map functions
@docs map9, map10, map11, map12
@docs map9, map10, map11, map12, map13
-}
@ -185,3 +185,36 @@ map12 func da db dc dd de df dg dh di dj dk dl =
(D.map2 Tuple.pair dg dh)
(D.map2 Tuple.pair di dj)
(D.map2 Tuple.pair dk dl)
{-| Try 12 decoders and combine the result.
-}
map13 :
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> value)
-> D.Decoder a
-> D.Decoder b
-> D.Decoder c
-> D.Decoder d
-> D.Decoder e
-> D.Decoder f
-> D.Decoder g
-> D.Decoder h
-> D.Decoder i
-> D.Decoder j
-> D.Decoder k
-> D.Decoder l
-> D.Decoder m
-> D.Decoder value
map13 func da db dc dd de df dg dh di dj dk dl dm =
D.map8
(\a b c ( d, e ) ( f, g ) ( h, i ) ( j, k ) ( l, m ) ->
func a b c d e f g h i j k l m
)
da
db
dc
(D.map2 Tuple.pair dd de)
(D.map2 Tuple.pair df dg)
(D.map2 Tuple.pair dh di)
(D.map2 Tuple.pair dj dk)
(D.map2 Tuple.pair dl dm)

View File

@ -1,11 +1,11 @@
module Internal.Tools.Json exposing
( Coder, string, bool, int, float, value
( Coder, string, bool, int, float, value, unit
, Encoder, encode, Decoder, decode, Value
, succeed, fail, andThen, lazy, map
, Docs(..), RequiredField(..), toDocs
, list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
, Field, field, parser
, object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12
, object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12, object13
)
{-|
@ -29,7 +29,7 @@ data types. Because this module uses dynamic builder types, this also means it
is relatively easy to write documentation for any data type that uses this
module to build its encoders and decoders.
@docs Coder, string, bool, int, float, value
@docs Coder, string, bool, int, float, value, unit
## JSON Coding
@ -62,7 +62,7 @@ first.
Once all fields are constructed, the user can create JSON objects.
@docs object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12
@docs object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12, object13
-}
@ -165,6 +165,7 @@ type Docs
| DocsRiskyMap (Descriptive { content : Docs, failure : List String })
| DocsSet Docs
| DocsString
| DocsUnit
| DocsValue
@ -477,13 +478,14 @@ iddict (Coder old) =
Coder
{ encoder = Iddict.encode old.encoder
, decoder =
D.andThen
(\( out, logs ) ->
D.succeed out
|> Iddict.decoder
|> D.map (\o -> ( o, logs ))
Iddict.decoder old.decoder
|> D.map
(\out ->
( Iddict.map (always Tuple.first) out
, Iddict.values out
|> List.concatMap Tuple.second
)
)
old.decoder
, docs = DocsIddict old.docs
}
@ -1271,6 +1273,85 @@ object12 { name, description, init } fa fb fc fd fe ff fg fh fi fj fk fl =
}
{-| Define an object with 13 keys
-}
object13 :
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> m -> object }
-> Field a object
-> Field b object
-> Field c object
-> Field d object
-> Field e object
-> Field f object
-> Field g object
-> Field h object
-> Field i object
-> Field j object
-> Field k object
-> Field l object
-> Field m object
-> Coder object
object13 { name, description, init } fa fb fc fd fe ff fg fh fi fj fk fl fm =
Coder
{ encoder =
objectEncoder
[ toEncodeField fa
, toEncodeField fb
, toEncodeField fc
, toEncodeField fd
, toEncodeField fe
, toEncodeField ff
, toEncodeField fg
, toEncodeField fh
, toEncodeField fi
, toEncodeField fj
, toEncodeField fk
, toEncodeField fl
, toEncodeField fm
]
, decoder =
D.map13
(\( a, la ) ( b, lb ) ( c, lc ) ( d, ld ) ( e, le ) ( f, lf ) ( g, lg ) ( h, lh ) ( i, li ) ( j, lj ) ( k, lk ) ( l, ll ) ( m, lm ) ->
( init a b c d e f g h i j k l m
, List.concat [ la, lb, lc, ld, le, lf, lg, lh, li, lj, lk, ll, lm ]
)
)
(toDecoderField fa)
(toDecoderField fb)
(toDecoderField fc)
(toDecoderField fd)
(toDecoderField fe)
(toDecoderField ff)
(toDecoderField fg)
(toDecoderField fh)
(toDecoderField fi)
(toDecoderField fj)
(toDecoderField fk)
(toDecoderField fl)
(toDecoderField fm)
, docs =
DocsObject
{ name = name
, description = description
, keys =
[ toDocsField fa
, toDocsField fb
, toDocsField fc
, toDocsField fd
, toDocsField fe
, toDocsField ff
, toDocsField fg
, toDocsField fh
, toDocsField fi
, toDocsField fj
, toDocsField fk
, toDocsField fl
, toDocsField fm
]
}
}
{-| Define a parser that converts a string into a custom Elm type.
-}
parser : { name : String, p : P.Parser ( a, List Log ), toString : a -> String } -> Coder a
@ -1382,6 +1463,18 @@ toEncodeField (Field data) =
( data.fieldName, data.toField >> data.encoder )
{-| Completely ignore whatever needs to be encoded, and simply return a unit
value.
-}
unit : Coder ()
unit =
Coder
{ encoder = \() -> E.object []
, decoder = D.succeed ( (), [] )
, docs = DocsUnit
}
{-| Do not do anything useful with a JSON value, just bring it to Elm as a
JavaScript value.
-}

View File

@ -71,7 +71,7 @@ import Internal.Config.Text as Text
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
import Internal.Tools.Json as Json
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
import Json.Encode as E
import Internal.Values.User as User exposing (User)
import Set exposing (Set)
import Time
@ -102,6 +102,7 @@ type alias Context =
, serverName : String
, suggestedAccessToken : Maybe String
, transaction : Maybe String
, user : Maybe User
, username : Maybe String
, versions : Maybe Versions
}
@ -153,7 +154,7 @@ fromApiFormat (APIContext c) =
-}
coder : Json.Coder Context
coder =
Json.object12
Json.object13
{ name = Text.docs.context.name
, description = Text.docs.context.description
, init = Context
@ -228,6 +229,13 @@ coder =
, coder = Json.string
}
)
(Json.field.optional.value
{ fieldName = "user"
, toField = .user
, description = Text.fields.context.user
, coder = User.coder
}
)
(Json.field.optional.value
{ fieldName = "username"
, toField = .username
@ -306,8 +314,8 @@ encode =
{-| A basic, untouched version of the Context, containing no information.
-}
init : String -> Context
init sn =
init : String -> Maybe User -> Context
init sn mu =
{ accessTokens = Hashdict.empty .value
, baseUrl = Nothing
, deviceId = Nothing
@ -318,6 +326,7 @@ init sn =
, serverName = sn
, suggestedAccessToken = Nothing
, transaction = Nothing
, user = mu
, username = Nothing
, versions = Nothing
}

View File

@ -56,6 +56,9 @@ import Internal.Tools.Json as Json
import Internal.Tools.Timestamp exposing (Timestamp)
import Internal.Values.Context as Context exposing (AccessToken, Context, Versions)
import Internal.Values.Settings as Settings
import Internal.Values.User exposing (User)
import Recursion
import Recursion.Fold
{-| There are lots of different data types in the Elm SDK, and many of them
@ -85,6 +88,7 @@ type EnvelopeUpdate a
| SetNextBatch String
| SetNow Timestamp
| SetRefreshToken String
| SetUser User
| SetVersions Versions
@ -186,10 +190,10 @@ getContent =
{-| Create a new enveloped data type. All settings are set to default values
from the [Internal.Config.Default](Internal-Config-Default) module.
-}
init : { serverName : String, content : a } -> Envelope a
init : { content : a, serverName : String, user : Maybe User } -> Envelope a
init data =
{ content = data.content
, context = Context.init data.serverName
, context = Context.init data.serverName data.user
, settings = Settings.init
}
@ -292,50 +296,97 @@ toMaybe data =
{-| Updates the Envelope with a given EnvelopeUpdate value.
-}
update : (au -> a -> a) -> EnvelopeUpdate au -> Envelope a -> Envelope a
update updateContent eu ({ context } as data) =
case eu of
update updateContent eu startData =
Recursion.runRecursion
(\updt ->
case updt of
ContentUpdate v ->
Recursion.base
(\data ->
{ data | content = updateContent v data.content }
)
HttpRequest _ ->
data
Recursion.base identity
More items ->
List.foldl (update updateContent) data items
Recursion.Fold.foldList (<<) identity items
Optional (Just u) ->
update updateContent u data
Recursion.recurse u
Optional Nothing ->
data
Recursion.base identity
RemoveAccessToken token ->
{ data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } }
Recursion.base
(\({ context } as data) ->
{ data
| context =
{ context
| accessTokens =
Hashdict.removeKey token context.accessTokens
}
}
)
RemovePasswordIfNecessary ->
Recursion.base
(\({ context } as data) ->
if data.settings.removePasswordOnLogin then
{ data | context = { context | password = Nothing } }
else
data
)
SetAccessToken a ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } }
)
SetBaseUrl b ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | baseUrl = Just b } }
)
SetDeviceId d ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | deviceId = Just d } }
)
SetNextBatch nextBatch ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | nextBatch = Just nextBatch } }
)
SetNow n ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | now = Just n } }
)
SetRefreshToken r ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | refreshToken = Just r } }
)
SetUser u ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | user = Just u } }
)
SetVersions vs ->
Recursion.base
(\({ context } as data) ->
{ data | context = { context | versions = Just vs } }
)
)
eu
startData

View File

@ -58,7 +58,8 @@ import Internal.Values.Event as Event exposing (Event)
import Internal.Values.StateManager as StateManager exposing (StateManager)
import Internal.Values.Timeline as Timeline exposing (Timeline)
import Internal.Values.User exposing (User)
import Json.Encode as E
import Recursion
import Recursion.Fold
{-| The Batch is a group of new events from somewhere in the timeline.
@ -255,30 +256,35 @@ setAccountData key value room =
{-| Update the Room based on given instructions.
-}
update : RoomUpdate -> Room -> Room
update ru room =
update roomUpdate startRoom =
Recursion.runRecursion
(\ru ->
case ru of
AddEvent _ ->
-- TODO: Add event
room
Recursion.base identity
AddSync batch ->
addSync batch room
Recursion.base (addSync batch)
Invite _ ->
-- TODO: Invite user
room
Recursion.base identity
More items ->
List.foldl update room items
Recursion.Fold.foldList (<<) identity items
Optional (Just u) ->
update u room
Recursion.recurse u
Optional Nothing ->
room
Recursion.base identity
SetAccountData key value ->
setAccountData key value room
Recursion.base (setAccountData key value)
SetEphemeral eph ->
{ room | ephemeral = eph }
Recursion.base (\room -> { room | ephemeral = eph })
)
roomUpdate
startRoom

View File

@ -678,20 +678,21 @@ mostRecentFrom filter timeline ptr =
{ ptr = ptr, visited = Set.empty }
{-| Recount the Timeline's amount of filled batches. Since the Timeline
automatically tracks the count on itself, this is generally exclusively used in
specific scenarios like decoding JSON values.
-}
recountFilledBatches : Timeline -> Timeline
recountFilledBatches (Timeline tl) =
Timeline
{ tl
| filledBatches =
tl.batches
|> Iddict.values
|> List.filter (\v -> v.events /= [])
|> List.length
}
-- {-| Recount the Timeline's amount of filled batches. Since the Timeline
-- automatically tracks the count on itself, this is generally exclusively used in
-- specific scenarios like decoding JSON values.
-- -}
-- recountFilledBatches : Timeline -> Timeline
-- recountFilledBatches (Timeline tl) =
-- Timeline
-- { tl
-- | filledBatches =
-- tl.batches
-- |> Iddict.values
-- |> List.filter (\v -> v.events /= [])
-- |> List.length
-- }
{-| Create a timeline with a single batch inserted. This batch is considered the

View File

@ -1,8 +1,9 @@
module Internal.Values.Vault exposing
( Vault, init
, VaultUpdate(..), update
, fromRoomId, mapRoom, updateRoom
, rooms, fromRoomId, mapRoom, updateRoom
, getAccountData, setAccountData
, coder
)
{-| This module hosts the Vault module. The Vault is the data type storing all
@ -23,13 +24,18 @@ To update the Vault, one uses VaultUpdate types.
Rooms are environments where people can have a conversation with each other.
@docs fromRoomId, mapRoom, updateRoom
@docs rooms, fromRoomId, mapRoom, updateRoom
## Account data
@docs getAccountData, setAccountData
## JSON
@docs coder
-}
import FastDict as Dict exposing (Dict)
@ -38,6 +44,8 @@ import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
import Internal.Tools.Json as Json
import Internal.Values.Room as Room exposing (Room)
import Internal.Values.User as User exposing (User)
import Recursion
import Recursion.Fold
{-| This is the Vault type.
@ -46,7 +54,6 @@ type alias Vault =
{ accountData : Dict String Json.Value
, nextBatch : Maybe String
, rooms : Hashdict Room
, user : Maybe User
}
@ -60,12 +67,13 @@ type VaultUpdate
| Optional (Maybe VaultUpdate)
| SetAccountData String Json.Value
| SetNextBatch String
| SetUser User
{-| Convert a Vault to and from a JSON object.
-}
coder : Json.Coder Vault
coder =
Json.object4
Json.object3
{ name = Text.docs.vault.name
, description = Text.docs.vault.description
, init = Vault
@ -91,13 +99,6 @@ coder =
, coder = Hashdict.coder .roomId Room.coder
}
)
(Json.field.optional.value
{ fieldName = "user"
, toField = .user
, description = Text.fields.vault.user
, coder = User.coder
}
)
{-| Get a given room by its room id.
@ -116,12 +117,11 @@ getAccountData key vault =
{-| Initiate a new Vault type.
-}
init : Maybe User -> Vault
init mUser =
init : Vault
init =
{ accountData = Dict.empty
, nextBatch = Nothing
, rooms = Hashdict.empty .roomId
, user = mUser
}
@ -133,6 +133,13 @@ mapRoom roomId f vault =
{ vault | rooms = Hashdict.map roomId f vault.rooms }
{-| Get a list of all joined rooms present in the vault.
-}
rooms : Vault -> List Room
rooms vault =
Hashdict.values vault.rooms
{-| Set a piece of account data as information in the global vault data.
-}
setAccountData : String -> Json.Value -> Vault -> Vault
@ -150,30 +157,35 @@ updateRoom roomId f vault =
{-| Update the Vault using a VaultUpdate type.
-}
update : VaultUpdate -> Vault -> Vault
update vu vault =
update vaultUpdate startVault =
Recursion.runRecursion
(\vu ->
case vu of
CreateRoomIfNotExists roomId ->
updateRoom roomId
(Maybe.withDefault (Room.init roomId) >> Maybe.Just)
vault
|> updateRoom roomId
|> Recursion.base
MapRoom roomId ru ->
mapRoom roomId (Room.update ru) vault
Recursion.base (mapRoom roomId (Room.update ru))
More items ->
List.foldl update vault items
Recursion.Fold.foldList (<<) identity items
Optional (Just u) ->
update u vault
Recursion.recurse u
Optional Nothing ->
vault
Recursion.base identity
SetAccountData key value ->
setAccountData key value vault
Recursion.base (setAccountData key value)
SetNextBatch nb ->
Recursion.base
(\vault ->
{ vault | nextBatch = Just nb }
SetUser user ->
{ vault | user = Just user }
)
)
vaultUpdate
startVault

View File

@ -1,6 +1,8 @@
module Matrix exposing
( Vault, fromUserId, fromUsername
, VaultUpdate, update, sync, logs
, rooms, fromRoomId
, getAccountData, setAccountData
, addAccessToken, sendMessageEvent
)
@ -27,6 +29,16 @@ support a monolithic public registry. (:
@docs VaultUpdate, update, sync, logs
## Exploring the Vault
@docs rooms, fromRoomId
## Account data
@docs getAccountData, setAccountData
## Debugging
@docs addAccessToken, sendMessageEvent
@ -66,6 +78,21 @@ addAccessToken token (Vault vault) =
|> Vault
{-| Get a room based on its room ID, if the user is a member of that room.
-}
fromRoomId : String -> Vault -> Maybe Types.Room
fromRoomId roomId (Vault vault) =
Envelope.mapMaybe (Internal.fromRoomId roomId) vault
|> Maybe.map Types.Room
{-| Get global account data.
-}
getAccountData : String -> Vault -> Maybe E.Value
getAccountData key (Vault vault) =
Envelope.extract (Internal.getAccountData key) vault
{-| Use a fully-fledged Matrix ID to connect.
case Matrix.fromUserId "@alice:example.org" of
@ -83,8 +110,9 @@ fromUserId uid =
|> Maybe.map
(\u ->
Envelope.init
{ serverName = "https://" ++ User.domain u
, content = Internal.init (Just u)
{ content = Internal.init
, serverName = "https://" ++ User.domain u
, user = Just u
}
|> Envelope.mapContext (\c -> { c | username = Just uid })
)
@ -99,19 +127,28 @@ you can either insert `alice` or `@alice:example.org`.
-}
fromUsername : { username : String, host : String, port_ : Maybe Int } -> Vault
fromUsername { username, host, port_ } =
{ serverName =
{ content = Internal.init
, serverName =
port_
|> Maybe.map String.fromInt
|> Maybe.map ((++) ":")
|> Maybe.withDefault ""
|> (++) host
, content = Internal.init (User.fromString username)
, user = User.fromString username
}
|> Envelope.init
|> Envelope.mapContext (\c -> { c | username = Just username })
|> Vault
{-| Get a list of all the rooms that the user has joined.
-}
rooms : Vault -> List Types.Room
rooms (Vault vault) =
Envelope.mapList Internal.rooms vault
|> List.map Types.Room
{-| The VaultUpdate is a complex type that helps update the Vault. However,
it also contains a human output!
@ -175,6 +212,25 @@ sendMessageEvent data =
}
{-| Set global account data.
-}
setAccountData :
{ content : E.Value
, eventType : String
, room : Vault
, toMsg : Types.VaultUpdate -> msg
}
-> Cmd msg
setAccountData data =
case data.room of
Vault vault ->
Api.setAccountData vault
{ content = data.content
, eventType = data.eventType
, toMsg = Types.VaultUpdate >> data.toMsg
}
{-| Synchronize the Vault with the Matrix API.
Effectively, this task asks the Matrix API to provide the latest information,

View File

@ -1,6 +1,8 @@
module Matrix.Room exposing
( Room, mostRecentEvents
, getAccountData
( Room, mostRecentEvents, roomId
, getAccountData, setAccountData
, sendMessageEvent, sendStateEvent
, invite, kick, ban
)
{-|
@ -12,7 +14,7 @@ What is usually called a chat, a channel, a conversation or a group chat on
other platforms, the term used in Matrix is a "room". A room is a conversation
where a group of users talk to each other.
@docs Room, mostRecentEvents
@docs Room, mostRecentEvents, roomId
This module exposes various functions that help you inspect various aspects of
a room.
@ -33,10 +35,26 @@ data is linked to the user account: other logged in devices can see the account
data too, as the server synchronizes it, but the server shouldn´t show it to
other users.
@docs getAccountData
@docs getAccountData, setAccountData
## Sending events
Besides reading the latest events, one can also send new events to the Matrix
room. These events are JSON objects that can be shaped in any way or form that
you like. To help other users with decoding your JSON objects, you pass an
`eventType` string which helps them figure out the nature of your JSON object.
@docs inviteUser, sendMessageEvent, sendStateEvent
## Moderating users
@docs invite, kick, ban
-}
import Internal.Api.Main as Api
import Internal.Values.Envelope as Envelope
import Internal.Values.Room as Internal
import Json.Encode as E
@ -49,6 +67,26 @@ type alias Room =
Types.Room
{-| Ban a user from a room.
-}
ban :
{ reason : Maybe String
, room : Room
, toMsg : Types.VaultUpdate -> msg
, user : Types.User
}
-> Cmd msg
ban data =
case ( data.room, data.user ) of
( Room room, Types.User user ) ->
Api.kickUser room
{ reason = data.reason
, roomId = roomId data.room
, toMsg = Types.VaultUpdate >> data.toMsg
, user = Envelope.getContent user
}
{-| Get a piece of account data linked to a certain string key.
-}
getAccountData : String -> Room -> Maybe E.Value
@ -56,9 +94,121 @@ getAccountData key (Room room) =
Envelope.extract (Internal.getAccountData key) room
{-| Invite a user to a room.
-}
invite :
{ reason : Maybe String
, room : Room
, toMsg : Types.VaultUpdate -> msg
, user : Types.User
}
-> Cmd msg
invite data =
case ( data.room, data.user ) of
( Room room, Types.User user ) ->
Api.inviteUser room
{ reason = data.reason
, roomId = roomId data.room
, toMsg = Types.VaultUpdate >> data.toMsg
, user = Envelope.getContent user
}
{-| Kick a user from a room.
-}
kick :
{ reason : Maybe String
, room : Room
, toMsg : Types.VaultUpdate -> msg
, user : Types.User
}
-> Cmd msg
kick data =
case ( data.room, data.user ) of
( Room room, Types.User user ) ->
Api.kickUser room
{ reason = data.reason
, roomId = roomId data.room
, toMsg = Types.VaultUpdate >> data.toMsg
, user = Envelope.getContent user
}
{-| Get a room's room id. This is an opaque string that distinguishes rooms from
each other.
-}
roomId : Room -> String
roomId (Room room) =
Envelope.extract .roomId room
{-| Get a list of the most recent events sent in the room.
-}
mostRecentEvents : Room -> List Types.Event
mostRecentEvents (Room room) =
Envelope.mapList Internal.mostRecentEvents room
|> List.map Types.Event
{-| Send a message event to a given room.
-}
sendMessageEvent :
{ content : E.Value
, eventType : String
, room : Room
, toMsg : Types.VaultUpdate -> msg
, transactionId : String
}
-> Cmd msg
sendMessageEvent data =
case data.room of
Room room ->
Api.sendMessageEvent room
{ content = data.content
, eventType = data.eventType
, roomId = roomId data.room
, toMsg = Types.VaultUpdate >> data.toMsg
, transactionId = data.transactionId
}
{-| Send a state event to a given room.
-}
sendStateEvent :
{ content : E.Value
, eventType : String
, room : Room
, stateKey : String
, toMsg : Types.VaultUpdate -> msg
}
-> Cmd msg
sendStateEvent data =
case data.room of
Room room ->
Api.sendStateEvent room
{ content = data.content
, eventType = data.eventType
, roomId = roomId data.room
, stateKey = data.stateKey
, toMsg = Types.VaultUpdate >> data.toMsg
}
{-| Set account data to a Matrix room.
-}
setAccountData :
{ content : E.Value
, eventType : String
, room : Room
, toMsg : Types.VaultUpdate -> msg
}
-> Cmd msg
setAccountData data =
case data.room of
Room room ->
Api.setRoomAccountData room
{ content = data.content
, eventType = data.eventType
, roomId = roomId data.room
, toMsg = Types.VaultUpdate >> data.toMsg
}

View File

@ -5,11 +5,10 @@ import Fuzz exposing (Fuzzer)
import Internal.Config.Leaks as Leaks
import Internal.Tools.Hashdict as Hashdict
import Internal.Values.Context as Context exposing (Context, Versions)
import Json.Decode as D
import Json.Encode as E
import Set
import Test exposing (..)
import Test.Tools.Timestamp as TestTimestamp
import Test.Values.User as TestUser
fuzzer : Fuzzer Context
@ -19,20 +18,26 @@ fuzzer =
maybeString =
Fuzz.maybe Fuzz.string
in
Fuzz.map8 (\a b c d e ( f, g ) ( h, i ) ( j, k ) -> Context a b c d e f g h i j k)
Fuzz.map8 (\a b c ( d, e ) ( f, g ) ( h, i ) ( j, k ) ( l, m ) -> Context a b c d e f g h i j k l m)
(Fuzz.constant <| Hashdict.empty .value)
maybeString
maybeString
(Fuzz.maybe TestTimestamp.fuzzer)
maybeString
(Fuzz.pair
maybeString
Fuzz.string
(Fuzz.maybe TestTimestamp.fuzzer)
)
(Fuzz.pair
maybeString
maybeString
)
(Fuzz.pair
Fuzz.string
maybeString
)
(Fuzz.pair
maybeString
(Fuzz.maybe TestUser.fuzzer)
)
(Fuzz.pair
maybeString
(Fuzz.maybe <| versionsFuzzer)

View File

@ -3,10 +3,7 @@ module Test.Values.Envelope exposing (..)
import Expect
import Fuzz exposing (Fuzzer)
import Internal.Config.Default as Default
import Internal.Tools.Json as Json
import Internal.Values.Envelope as Envelope exposing (Envelope)
import Json.Decode as D
import Json.Encode as E
import Test exposing (..)
import Test.Values.Context as TestContext
import Test.Values.Settings as TestSettings
@ -28,7 +25,7 @@ suite =
[ fuzz Fuzz.string
"currentVersion"
(\s ->
{ content = s, serverName = "" }
{ content = s, serverName = "", user = Nothing }
|> Envelope.init
|> Envelope.extractSettings .currentVersion
|> Expect.equal Default.currentVersion
@ -36,7 +33,7 @@ suite =
, fuzz Fuzz.string
"deviceName"
(\s ->
{ content = s, serverName = "" }
{ content = s, serverName = "", user = Nothing }
|> Envelope.init
|> Envelope.extractSettings .deviceName
|> Expect.equal Default.deviceName
@ -44,7 +41,7 @@ suite =
, fuzz Fuzz.string
"syncTime"
(\s ->
{ content = s, serverName = "" }
{ content = s, serverName = "", user = Nothing }
|> Envelope.init
|> Envelope.extractSettings .syncTime
|> Expect.equal Default.syncTime

View File

@ -41,16 +41,18 @@ fuzzerState =
unsignedDataFuzzer : Fuzzer Event.UnsignedData
unsignedDataFuzzer =
Fuzz.map4
(\age prev redact trans ->
Fuzz.map5
(\age memb prev redact trans ->
Event.UnsignedData
{ age = age
, membership = memb
, prevContent = prev
, redactedBecause = redact
, transactionId = trans
}
)
(Fuzz.maybe Fuzz.int)
(Fuzz.maybe Fuzz.string)
(Fuzz.maybe valueFuzzer)
(Fuzz.maybe <| Fuzz.lazy (\_ -> fuzzer))
(Fuzz.maybe Fuzz.string)

View File

@ -4,8 +4,6 @@ import Fuzz exposing (Fuzzer)
import Internal.Values.Room as Room exposing (Room)
import Json.Encode as E
import Test exposing (..)
import Test.Filter.Timeline as TestFilter
import Test.Values.Event as TestEvent
placeholderValue : E.Value

View File

@ -11,7 +11,7 @@ import Test exposing (..)
fuzzer : Fuzzer Settings
fuzzer =
Fuzz.map4 Settings
Fuzz.map5 Settings
(Fuzz.oneOf
[ Fuzz.constant Default.currentVersion
, Fuzz.string
@ -22,6 +22,7 @@ fuzzer =
, Fuzz.string
]
)
(Fuzz.maybe Fuzz.string)
(Fuzz.oneOf
[ Fuzz.constant Default.removePasswordOnLogin
, Fuzz.bool

View File

@ -6,6 +6,7 @@ import Internal.Filter.Timeline as Filter
import Internal.Tools.Json as Json
import Internal.Values.Timeline as Timeline exposing (Batch, Timeline)
import Json.Decode as D
import Json.Encode as E
import Test exposing (..)
import Test.Filter.Timeline as TestFilter
@ -250,7 +251,8 @@ suite =
(\timeline ->
timeline
|> Json.encode Timeline.coder
|> D.decodeValue (Json.decode Timeline.coder)
|> E.encode 0
|> D.decodeString (Json.decode Timeline.coder)
|> Result.map Tuple.first
|> Result.map (Timeline.mostRecentEvents Filter.pass)
|> Expect.equal (Ok <| Timeline.mostRecentEvents Filter.pass timeline)

View File

@ -7,12 +7,11 @@ import Internal.Values.Vault exposing (Vault)
import Test exposing (..)
import Test.Tools.Hashdict as TestHashdict
import Test.Values.Room as TestRoom
import Test.Values.User as TestUser
vault : Fuzzer Vault
vault =
Fuzz.map4 Vault
Fuzz.map3 Vault
(Fuzz.string
|> Fuzz.map (\k -> ( k, Json.encode Json.int 0 ))
|> Fuzz.list
@ -20,4 +19,3 @@ vault =
)
(Fuzz.maybe Fuzz.string)
(TestHashdict.fuzzer .roomId TestRoom.fuzzer)
(Fuzz.maybe TestUser.fuzzer)