Compare commits
	
		
			5 Commits 
		
	
	
		
			f5f5c14e10
			...
			d12466e82a
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
								 | 
						d12466e82a | |
| 
							
							
								
								 | 
						4b92d9ea20 | |
| 
							
							
								
								 | 
						4777de5b67 | |
| 
							
							
								
								 | 
						865e83cdae | |
| 
							
							
								
								 | 
						2cb21dc102 | 
							
								
								
									
										3
									
								
								elm.json
								
								
								
								
							
							
						
						
									
										3
									
								
								elm.json
								
								
								
								
							| 
						 | 
				
			
			@ -19,7 +19,8 @@
 | 
			
		|||
        "elm/http": "2.0.0 <= v < 3.0.0",
 | 
			
		||||
        "elm/json": "1.0.0 <= v < 2.0.0",
 | 
			
		||||
        "elm/time": "1.0.0 <= v < 2.0.0",
 | 
			
		||||
        "elm/url": "1.0.0 <= v < 2.0.0"
 | 
			
		||||
        "elm/url": "1.0.0 <= v < 2.0.0",
 | 
			
		||||
        "miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0"
 | 
			
		||||
    },
 | 
			
		||||
    "test-dependencies": {}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,30 +31,52 @@ type as a message to the Vault to update certain information.
 | 
			
		|||
-}
 | 
			
		||||
 | 
			
		||||
import Http
 | 
			
		||||
import Internal.Api.Helpers as Helpers
 | 
			
		||||
import Internal.Tools.Context as Context exposing (Context)
 | 
			
		||||
import Internal.Tools.Exceptions as X
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
{-| The TaskChain is a piece in the long chain of tasks that need to be completed.
 | 
			
		||||
The type defines four variables:
 | 
			
		||||
 | 
			
		||||
- `err` value that may arise on an error
 | 
			
		||||
- `u`   the update msg that should be returned
 | 
			
		||||
- `a`   phantom type before executing the chain's context
 | 
			
		||||
- `b`   phantom type after  executing the chain's context
 | 
			
		||||
-}
 | 
			
		||||
type alias TaskChain err u a b =
 | 
			
		||||
    Context a -> Task (FailedChainPiece err u) (TaskChainPiece u a b)
 | 
			
		||||
 | 
			
		||||
{-| An IdemChain is a TaskChain that does not influence the chain's context
 | 
			
		||||
 | 
			
		||||
- `err` value that may arise on an error
 | 
			
		||||
- `u`   the update msg that should be executed
 | 
			
		||||
- `a`   phantom type before, during and after the chain's context
 | 
			
		||||
-}
 | 
			
		||||
type alias IdemChain err u a =
 | 
			
		||||
    TaskChain err u a a
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| A CompleteChain is a complete snake that can be safely run and executed by
 | 
			
		||||
the Elm core.
 | 
			
		||||
-}
 | 
			
		||||
type alias CompleteChain u =
 | 
			
		||||
    TaskChain () u {} {}
 | 
			
		||||
 | 
			
		||||
{-| A TaskChainPiece is a piece that updates the chain's context.
 | 
			
		||||
 | 
			
		||||
Once a chain is executed, the process will add the `messages` value to its list
 | 
			
		||||
of updates, and it will update its context according to the `contextChange`
 | 
			
		||||
function.
 | 
			
		||||
-}
 | 
			
		||||
type alias TaskChainPiece u a b =
 | 
			
		||||
    { contextChange : Context a -> Context b
 | 
			
		||||
    , messages : List u
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| A FailedChainPiece initiates an early breakdown of a chain. Unless caught,
 | 
			
		||||
this halts execution of the chain. The process will add the `messages` value to
 | 
			
		||||
its list of updates, and it will return the given `err` value for a direct
 | 
			
		||||
explanation of what went wrong.
 | 
			
		||||
-}
 | 
			
		||||
type alias FailedChainPiece err u =
 | 
			
		||||
    { error : err, messages : List u }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,46 +37,3 @@ ratelimited task =
 | 
			
		|||
                    _ ->
 | 
			
		||||
                        Task.fail e
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Sometimes, you don't really care if something went wrong - you just want to try again.
 | 
			
		||||
 | 
			
		||||
This task will only return an error if it went wrong on the n'th attempt.
 | 
			
		||||
 | 
			
		||||
-}
 | 
			
		||||
retryTask : Int -> Task X.Error a -> Task X.Error a
 | 
			
		||||
retryTask n task =
 | 
			
		||||
    if n <= 0 then
 | 
			
		||||
        task
 | 
			
		||||
 | 
			
		||||
    else
 | 
			
		||||
        Task.onError
 | 
			
		||||
            (\err ->
 | 
			
		||||
                let
 | 
			
		||||
                    retry : Task X.Error a
 | 
			
		||||
                    retry =
 | 
			
		||||
                        retryTask (n - 1) task
 | 
			
		||||
                in
 | 
			
		||||
                case err of
 | 
			
		||||
                    X.InternetException (Http.BadUrl _) ->
 | 
			
		||||
                        Task.fail err
 | 
			
		||||
 | 
			
		||||
                    X.InternetException _ ->
 | 
			
		||||
                        retry
 | 
			
		||||
 | 
			
		||||
                    X.SDKException (X.ServerReturnsBadJSON _) ->
 | 
			
		||||
                        retry
 | 
			
		||||
 | 
			
		||||
                    X.SDKException _ ->
 | 
			
		||||
                        Task.fail err
 | 
			
		||||
 | 
			
		||||
                    X.ServerException _ ->
 | 
			
		||||
                        Task.fail err
 | 
			
		||||
 | 
			
		||||
                    X.ContextFailed _ ->
 | 
			
		||||
                        Task.fail err
 | 
			
		||||
 | 
			
		||||
                    X.UnsupportedSpecVersion ->
 | 
			
		||||
                        Task.fail err
 | 
			
		||||
            )
 | 
			
		||||
            task
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,9 +20,9 @@ Note that **under development** doesn't always mean that it _will be_ supported.
 | 
			
		|||
 | 
			
		||||
| **Spec version** |   | Syncing | Redaction |
 | 
			
		||||
| ---------------- | - | ------- | --------- |
 | 
			
		||||
| v1.8   || ⚡ | ✔️ |
 | 
			
		||||
| v1.7   || ⚡ | ✔️ |
 | 
			
		||||
| v1.6   || ⚠️ | ✔️ |
 | 
			
		||||
| v1.8   || ✔️ | ✔️ |
 | 
			
		||||
| v1.7   || ✔️ | ✔️ |
 | 
			
		||||
| v1.6   || ✔️ | ✔️ |
 | 
			
		||||
| v1.5   || ✔️ | ✔️ |
 | 
			
		||||
| v1.4   || ✔️ | ✔️ |
 | 
			
		||||
| v1.3   || ✔️ | ✔️ |
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +64,8 @@ Note that **under development** doesn't always mean that it _will be_ supported.
 | 
			
		|||
 | 
			
		||||
| **Spec version** |   | Event | Joined members | Event at timestamp |
 | 
			
		||||
| ---------------- | - | ----- | -------------- | ------------------ |
 | 
			
		||||
| v1.8   || ✔️ | ✔️ | ⚡ |
 | 
			
		||||
| v1.7   || ✔️ | ✔️ | ⚡ |
 | 
			
		||||
| v1.8   || ✔️ | ✔️ | ⚠️ |
 | 
			
		||||
| v1.7   || ✔️ | ✔️ | ⚠️ |
 | 
			
		||||
| v1.6   || ✔️ | ✔️ | ⚠️ |
 | 
			
		||||
| v1.5   || ✔️ | ✔️ | ⛔ |
 | 
			
		||||
| v1.4   || ✔️ | ✔️ | ⛔ |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
module Internal.Api.Snackbar exposing (..)
 | 
			
		||||
 | 
			
		||||
{-| The snackbar module helps wraps relevant credentials, access tokens, refresh tokens and more around internal types.
 | 
			
		||||
{-| The snackbar module helps wraps relevant credentials, access tokens, 
 | 
			
		||||
refresh tokens and more around internal types.
 | 
			
		||||
 | 
			
		||||
Vault, Room and Event types don't need access to API tokens,
 | 
			
		||||
but a user may way to redact an event, leave a room or reject an invite.
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +15,7 @@ without needing to update every data type whenever any of the tokens change.
 | 
			
		|||
 | 
			
		||||
import Dict exposing (Dict)
 | 
			
		||||
import Internal.Api.Versions.V1.Versions as V
 | 
			
		||||
import Internal.Config.DefaultSettings as DS
 | 
			
		||||
import Internal.Tools.LoginValues as Login exposing (AccessToken(..))
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,9 +29,14 @@ type Snackbar a vu
 | 
			
		|||
        , homeserver : String
 | 
			
		||||
        , transactionOffset : Int
 | 
			
		||||
        , vs : Maybe V.Versions
 | 
			
		||||
        , settings : Settings
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type alias Settings =
 | 
			
		||||
    { syncTimeout : Int }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
accessToken : Snackbar a vu -> AccessToken
 | 
			
		||||
accessToken (Snackbar { access }) =
 | 
			
		||||
    access
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +99,9 @@ init data =
 | 
			
		|||
        , failedTasks = Dict.empty
 | 
			
		||||
        , failedTasksOffset = 0
 | 
			
		||||
        , homeserver = data.baseUrl
 | 
			
		||||
        , settings =
 | 
			
		||||
            { syncTimeout = DS.syncTimeout
 | 
			
		||||
            }
 | 
			
		||||
        , transactionOffset = 0
 | 
			
		||||
        , vs = Nothing
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +115,7 @@ map f (Snackbar data) =
 | 
			
		|||
        , failedTasks = data.failedTasks
 | 
			
		||||
        , failedTasksOffset = 0
 | 
			
		||||
        , homeserver = data.homeserver
 | 
			
		||||
        , settings = data.settings
 | 
			
		||||
        , transactionOffset = data.transactionOffset
 | 
			
		||||
        , vs = data.vs
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +146,11 @@ setTransactionOffset i (Snackbar data) =
 | 
			
		|||
    Snackbar { data | transactionOffset = max (data.transactionOffset + 1) (i + 1) }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
updateSettings : (Settings -> Settings) -> Snackbar a vu -> Snackbar a vu
 | 
			
		||||
updateSettings f (Snackbar ({ settings } as data)) =
 | 
			
		||||
    Snackbar { data | settings = f settings }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
userId : Snackbar a vu -> Maybe String
 | 
			
		||||
userId (Snackbar { access }) =
 | 
			
		||||
    Login.getUserId access
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,3 +33,10 @@ supportedVersions =
 | 
			
		|||
defaultDeviceName : String
 | 
			
		||||
defaultDeviceName =
 | 
			
		||||
    "Elm Matrix SDK (v" ++ currentVersion ++ ")"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| The amount of seconds that the Matrix Vault should wait for a response from the Matrix homeserver.
 | 
			
		||||
-}
 | 
			
		||||
syncTimeout : Int
 | 
			
		||||
syncTimeout =
 | 
			
		||||
    10
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ import Internal.Values.Room as Internal
 | 
			
		|||
import Internal.Values.StateManager as StateManager
 | 
			
		||||
import Internal.Values.Timeline as Timeline
 | 
			
		||||
import Json.Encode as E
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
import Task
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| The `Room` type represents a Matrix Room. It contains context information
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
module Internal.Tools.DefaultDict exposing (..)
 | 
			
		||||
 | 
			
		||||
import FastDict as Dict exposing (Dict)
 | 
			
		||||
 | 
			
		||||
{-| A dictionary of keys and values that includes a default when a key doesn't exist.
 | 
			
		||||
-}
 | 
			
		||||
type DefaultDict k v
 | 
			
		||||
    = DefaultDict
 | 
			
		||||
        { content : Dict k v
 | 
			
		||||
        , default : v
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Create an empty dictionary that has a default value.
 | 
			
		||||
-}
 | 
			
		||||
empty : v -> DefaultDict k v
 | 
			
		||||
empty v =
 | 
			
		||||
    DefaultDict
 | 
			
		||||
        { content = Dict.empty
 | 
			
		||||
        , default = v
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Get the value associated with the key. Uses the default if not found. -}
 | 
			
		||||
get : comparable -> DefaultDict comparable v -> v
 | 
			
		||||
get k (DefaultDict data) =
 | 
			
		||||
    Dict.get k data.content |> Maybe.withDefault data.default
 | 
			
		||||
 | 
			
		||||
{-| Insert a key-value pair into a dictionary with a default.
 | 
			
		||||
-}
 | 
			
		||||
insert : comparable -> v -> DefaultDict comparable v -> DefaultDict comparable v
 | 
			
		||||
insert k v (DefaultDict data) =
 | 
			
		||||
    DefaultDict { data | content = Dict.insert k v data.content }
 | 
			
		||||
 | 
			
		||||
{-| "Remove" a value by making its value synchronize with the default value.
 | 
			
		||||
-}
 | 
			
		||||
remove : comparable -> DefaultDict comparable v -> DefaultDict comparable v
 | 
			
		||||
remove k (DefaultDict data) =
 | 
			
		||||
    DefaultDict { data | content = Dict.remove k data.content }
 | 
			
		||||
 | 
			
		||||
{-| Update the default value of all unset keys.
 | 
			
		||||
-}
 | 
			
		||||
setDefault : v -> DefaultDict k v -> DefaultDict k v
 | 
			
		||||
setDefault v (DefaultDict data) =
 | 
			
		||||
    DefaultDict { data | default = v }
 | 
			
		||||
 | 
			
		||||
{-| Update the value of a dictionary. The returned (or received) value is `Nothing`,
 | 
			
		||||
it means the key synchronizes with the default value.
 | 
			
		||||
-}
 | 
			
		||||
update : comparable -> (Maybe v -> Maybe v) -> DefaultDict comparable v -> DefaultDict comparable v
 | 
			
		||||
update k fv (DefaultDict data) =
 | 
			
		||||
    DefaultDict { data | content = Dict.update k fv data.content }
 | 
			
		||||
 | 
			
		||||
{-| Update the default value.
 | 
			
		||||
-}
 | 
			
		||||
updateDefault : (v -> v) -> DefaultDict k v -> DefaultDict k v
 | 
			
		||||
updateDefault f (DefaultDict data) =
 | 
			
		||||
    DefaultDict { data | default = f data.default }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,384 @@
 | 
			
		|||
module Internal.Tools.Filters.Filter exposing (..)
 | 
			
		||||
 | 
			
		||||
import Internal.Tools.Filters.SpecObjects as SO
 | 
			
		||||
import Internal.Tools.Filters.SimpleFilter as SF exposing (SimpleFilter)
 | 
			
		||||
import Internal.Tools.SpecEnums as Enums
 | 
			
		||||
 | 
			
		||||
{-| Event filters tell the API what events to look for, 
 | 
			
		||||
but specifically for events that are unrelated to any room.
 | 
			
		||||
-}
 | 
			
		||||
type EventFilter
 | 
			
		||||
    = EventFilter
 | 
			
		||||
        { limit : Maybe Int
 | 
			
		||||
        , senders : SimpleFilter String
 | 
			
		||||
        , types : SimpleFilter String
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| The final type dictates how everything else behaves.
 | 
			
		||||
-}
 | 
			
		||||
type Filter =
 | 
			
		||||
    Filter
 | 
			
		||||
        { accountData : EventFilter
 | 
			
		||||
        , presence : EventFilter
 | 
			
		||||
        , room : RoomFilter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| RoomFilter types tell the API what is considered relevant in a room, 
 | 
			
		||||
and which rooms to include.
 | 
			
		||||
-}
 | 
			
		||||
type RoomFilter
 | 
			
		||||
    = RoomFilter
 | 
			
		||||
        { accountData : RoomEventFilter
 | 
			
		||||
        , ephemeral : RoomEventFilter
 | 
			
		||||
        , rooms : SimpleFilter String
 | 
			
		||||
        , timeline : RoomEventFilter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| RoomEventFilter types tell the API what events to look for, 
 | 
			
		||||
and what ones to ignore.
 | 
			
		||||
-}
 | 
			
		||||
type RoomEventFilter
 | 
			
		||||
    = RoomEventFilter
 | 
			
		||||
        { lazyLoadMembers : Bool
 | 
			
		||||
        , limit : Maybe Int
 | 
			
		||||
        , rooms : SimpleFilter String
 | 
			
		||||
        , senders : SimpleFilter String
 | 
			
		||||
        , types : SimpleFilter String
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
allEvents : EventFilter
 | 
			
		||||
allEvents =
 | 
			
		||||
    EventFilter
 | 
			
		||||
        { limit = Nothing
 | 
			
		||||
        , senders = SF.all
 | 
			
		||||
        , types = SF.all
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
allFilters : Filter
 | 
			
		||||
allFilters =
 | 
			
		||||
    Filter
 | 
			
		||||
        { accountData = allEvents
 | 
			
		||||
        , presence = allEvents
 | 
			
		||||
        , room = allRooms
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
allRooms : RoomFilter
 | 
			
		||||
allRooms =
 | 
			
		||||
    RoomFilter
 | 
			
		||||
        { accountData = allRoomEvents
 | 
			
		||||
        , ephemeral = allRoomEvents
 | 
			
		||||
        , rooms = SF.all
 | 
			
		||||
        , timeline = allRoomEvents
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
allRoomEvents : RoomEventFilter
 | 
			
		||||
allRoomEvents =
 | 
			
		||||
    RoomEventFilter
 | 
			
		||||
        { lazyLoadMembers = False
 | 
			
		||||
        , limit = Nothing
 | 
			
		||||
        , rooms = SF.all
 | 
			
		||||
        , senders = SF.all
 | 
			
		||||
        , types = SF.all
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
decodeEventFilter : SO.EventFilter -> EventFilter
 | 
			
		||||
decodeEventFilter data =
 | 
			
		||||
    EventFilter
 | 
			
		||||
        { limit = data.limit
 | 
			
		||||
        , senders = SF.toSimpleFilter data.senders data.notSenders
 | 
			
		||||
        , types = SF.toSimpleFilter data.types data.notTypes
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
decodeFilter : SO.Filter -> Filter
 | 
			
		||||
decodeFilter data =
 | 
			
		||||
    Filter
 | 
			
		||||
        { accountData =
 | 
			
		||||
            data.accountData
 | 
			
		||||
                |> Maybe.map decodeEventFilter
 | 
			
		||||
                |> Maybe.withDefault allEvents
 | 
			
		||||
        , presence =
 | 
			
		||||
            data.presence
 | 
			
		||||
                |> Maybe.map decodeEventFilter
 | 
			
		||||
                |> Maybe.withDefault allEvents
 | 
			
		||||
        , room =
 | 
			
		||||
            data.room
 | 
			
		||||
                |> Maybe.map decodeRoomFilter
 | 
			
		||||
                |> Maybe.withDefault allRooms
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Decode a RoomFilter from a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
decodeRoomFilter : SO.RoomFilter -> RoomFilter
 | 
			
		||||
decodeRoomFilter data =
 | 
			
		||||
    let
 | 
			
		||||
        decodeREF : Maybe SO.RoomEventFilter -> RoomEventFilter
 | 
			
		||||
        decodeREF =
 | 
			
		||||
            Maybe.map decodeRoomEventFilter >> Maybe.withDefault allRoomEvents
 | 
			
		||||
    in
 | 
			
		||||
        RoomFilter
 | 
			
		||||
            { accountData = decodeREF data.accountData
 | 
			
		||||
            , ephemeral = decodeREF data.ephemeral
 | 
			
		||||
            , rooms = SF.toSimpleFilter data.rooms data.notRooms
 | 
			
		||||
            , timeline = decodeREF data.timeline
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
{-| Decode a RoomEventFilter from a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
decodeRoomEventFilter : SO.RoomEventFilter -> RoomEventFilter
 | 
			
		||||
decodeRoomEventFilter data =
 | 
			
		||||
    RoomEventFilter
 | 
			
		||||
        { lazyLoadMembers = data.lazyLoadMembers
 | 
			
		||||
        , limit = data.limit
 | 
			
		||||
        , rooms = SF.toSimpleFilter data.rooms data.notRooms
 | 
			
		||||
        , senders = SF.toSimpleFilter data.senders data.notSenders
 | 
			
		||||
        , types = SF.toSimpleFilter data.types data.notTypes
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Encode an EventFilter into a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
encodeEventFilter : EventFilter -> SO.EventFilter
 | 
			
		||||
encodeEventFilter (EventFilter data) =
 | 
			
		||||
    { limit = data.limit
 | 
			
		||||
    , notSenders = SF.toExclude data.senders
 | 
			
		||||
    , notTypes = SF.toExclude data.types
 | 
			
		||||
    , senders = SF.toInclude data.senders
 | 
			
		||||
    , types = SF.toInclude data.types
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Encode a Filter into a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
encodeFilter : Filter -> SO.Filter
 | 
			
		||||
encodeFilter (Filter data) =
 | 
			
		||||
    { accountData = Just <| encodeEventFilter data.accountData
 | 
			
		||||
    , eventFields = Nothing
 | 
			
		||||
    , eventFormat = Enums.Client
 | 
			
		||||
    , presence = Just <| encodeEventFilter data.presence
 | 
			
		||||
    , room = Just <| encodeRoomFilter data.room
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Encode a RoomFilter into a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
encodeRoomFilter : RoomFilter -> SO.RoomFilter
 | 
			
		||||
encodeRoomFilter (RoomFilter data) =
 | 
			
		||||
    { accountData = Just <| encodeRoomEventFilter data.accountData
 | 
			
		||||
    , ephemeral = Just <| encodeRoomEventFilter data.ephemeral
 | 
			
		||||
    , includeLeave = False
 | 
			
		||||
    , notRooms = SF.toExclude data.rooms
 | 
			
		||||
    , rooms = SF.toInclude data.rooms
 | 
			
		||||
    , state = Just <| encodeRoomEventFilter data.timeline
 | 
			
		||||
    , timeline = Just <| encodeRoomEventFilter data.timeline
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Encode a RoomEventFilter into a spec-compliant format.
 | 
			
		||||
-}
 | 
			
		||||
encodeRoomEventFilter : RoomEventFilter -> SO.RoomEventFilter
 | 
			
		||||
encodeRoomEventFilter (RoomEventFilter data) =
 | 
			
		||||
    { containsUrl = Nothing
 | 
			
		||||
    , includeRedundantMembers = False
 | 
			
		||||
    , lazyLoadMembers = data.lazyLoadMembers
 | 
			
		||||
    , limit = data.limit
 | 
			
		||||
    , notRooms = SF.toExclude data.rooms
 | 
			
		||||
    , notSenders = SF.toExclude data.senders
 | 
			
		||||
    , notTypes = SF.toExclude data.types
 | 
			
		||||
    , rooms = SF.toInclude data.rooms
 | 
			
		||||
    , senders = SF.toInclude data.senders
 | 
			
		||||
    , types = SF.toInclude data.types
 | 
			
		||||
    , unreadThreadNotifications = True
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Flatten a filter.
 | 
			
		||||
-}
 | 
			
		||||
flattenFilter : Filter -> List (SimpleFilter String)
 | 
			
		||||
flattenFilter (Filter f) =
 | 
			
		||||
    List.concat
 | 
			
		||||
        [ flattenEventFilter f.accountData
 | 
			
		||||
        , flattenEventFilter f.presence
 | 
			
		||||
        , flattenRoomFilter f.room
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
{-| Flatten a EventFilter.
 | 
			
		||||
-}
 | 
			
		||||
flattenEventFilter : EventFilter -> List (SimpleFilter String)
 | 
			
		||||
flattenEventFilter (EventFilter f) = [ f.senders, f.types ]
 | 
			
		||||
 | 
			
		||||
{-| Flatten a RoomFilter.
 | 
			
		||||
-}
 | 
			
		||||
flattenRoomFilter : RoomFilter -> List (SimpleFilter String)
 | 
			
		||||
flattenRoomFilter (RoomFilter f) =
 | 
			
		||||
    [ f.accountData, f.ephemeral, f.timeline ]
 | 
			
		||||
        |> List.map flattenRoomEventFilter
 | 
			
		||||
        |> List.concat
 | 
			
		||||
        |> (::) f.rooms
 | 
			
		||||
 | 
			
		||||
{-| Flatten a RoomEventFilter.
 | 
			
		||||
-}
 | 
			
		||||
flattenRoomEventFilter : RoomEventFilter -> List (SimpleFilter String)
 | 
			
		||||
flattenRoomEventFilter (RoomEventFilter f) = [ f.rooms, f.senders, f.types ]
 | 
			
		||||
 | 
			
		||||
{-| Get an intersection of a Filter.
 | 
			
		||||
-}
 | 
			
		||||
intersectFilter : Filter -> Filter -> Filter
 | 
			
		||||
intersectFilter (Filter f1) (Filter f2) =
 | 
			
		||||
    Filter
 | 
			
		||||
        { accountData = intersectEventFilter f1.accountData f2.accountData
 | 
			
		||||
        , presence = intersectEventFilter f1.presence f2.presence
 | 
			
		||||
        , room = intersectRoomFilter f1.room f2.room
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Get an intersection of a EventFilter.
 | 
			
		||||
-}
 | 
			
		||||
intersectEventFilter : EventFilter -> EventFilter -> EventFilter
 | 
			
		||||
intersectEventFilter (EventFilter f1) (EventFilter f2) =
 | 
			
		||||
    EventFilter
 | 
			
		||||
        { limit =
 | 
			
		||||
            case (f1.limit, f2.limit) of
 | 
			
		||||
                (Just l1, Just l2) ->
 | 
			
		||||
                    Just (max l1 l2)
 | 
			
		||||
                
 | 
			
		||||
                (Just _, Nothing) ->
 | 
			
		||||
                    f1.limit
 | 
			
		||||
                
 | 
			
		||||
                (Nothing, Just _) ->
 | 
			
		||||
                    f2.limit
 | 
			
		||||
                
 | 
			
		||||
                (Nothing, Nothing) ->
 | 
			
		||||
                    Nothing
 | 
			
		||||
        , senders = SF.intersect f1.senders f2.senders
 | 
			
		||||
        , types = SF.intersect f1.types f2.types
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Get an intersection of a RoomFilter.
 | 
			
		||||
-}
 | 
			
		||||
intersectRoomFilter : RoomFilter -> RoomFilter -> RoomFilter
 | 
			
		||||
intersectRoomFilter (RoomFilter f1) (RoomFilter f2) =
 | 
			
		||||
    RoomFilter
 | 
			
		||||
        { accountData = intersectRoomEventFilter f1.accountData f2.accountData
 | 
			
		||||
        , ephemeral = intersectRoomEventFilter f1.ephemeral f2.ephemeral
 | 
			
		||||
        , rooms = SF.intersect f1.rooms f2.rooms
 | 
			
		||||
        , timeline = intersectRoomEventFilter f1.timeline f2.timeline
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Get an intersection of a RoomEventFilter.
 | 
			
		||||
-}
 | 
			
		||||
intersectRoomEventFilter : RoomEventFilter -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
intersectRoomEventFilter (RoomEventFilter f1) (RoomEventFilter f2) =
 | 
			
		||||
    RoomEventFilter
 | 
			
		||||
        { lazyLoadMembers = f1.lazyLoadMembers && f2.lazyLoadMembers
 | 
			
		||||
        , limit =
 | 
			
		||||
            case (f1.limit, f2.limit) of
 | 
			
		||||
                (Just l1, Just l2) ->
 | 
			
		||||
                    Just (max l1 l2)
 | 
			
		||||
                
 | 
			
		||||
                (Just _, Nothing) ->
 | 
			
		||||
                    f1.limit
 | 
			
		||||
                
 | 
			
		||||
                (Nothing, Just _) ->
 | 
			
		||||
                    f2.limit
 | 
			
		||||
                
 | 
			
		||||
                (Nothing, Nothing) ->
 | 
			
		||||
                    Nothing
 | 
			
		||||
        , rooms = SF.intersect f1.rooms f2.rooms
 | 
			
		||||
        , senders = SF.intersect f1.senders f2.senders
 | 
			
		||||
        , types = SF.intersect f1.types f2.types
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Check whether a filter is a subset of another filter.
 | 
			
		||||
-}
 | 
			
		||||
isSubSet : Filter -> Filter -> Bool
 | 
			
		||||
isSubSet f1 f2 =
 | 
			
		||||
    let
 | 
			
		||||
        isSame : List (SimpleFilter String) -> List (SimpleFilter String) -> Bool
 | 
			
		||||
        isSame l1 l2 =
 | 
			
		||||
            case (l1, l2) of
 | 
			
		||||
                (h1 :: t1, h2 :: t2) ->
 | 
			
		||||
                    SF.subset h1 h2 && isSame t1 t2
 | 
			
		||||
                ([], []) ->
 | 
			
		||||
                    True
 | 
			
		||||
                _ ->
 | 
			
		||||
                    False
 | 
			
		||||
    in
 | 
			
		||||
        isSame (flattenFilter f1) (flattenFilter f2)
 | 
			
		||||
 | 
			
		||||
lazyLoadMembers : Bool -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
lazyLoadMembers b (RoomEventFilter data) =
 | 
			
		||||
    RoomEventFilter { data | lazyLoadMembers = b }
 | 
			
		||||
 | 
			
		||||
{-| Determine a limit for the amount of events. If no limit is given, the homeserver decides this limit for itself.
 | 
			
		||||
-}
 | 
			
		||||
setEventLimit : Maybe Int -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
setEventLimit i (RoomEventFilter data) =
 | 
			
		||||
    RoomEventFilter { data | limit = i }
 | 
			
		||||
 | 
			
		||||
{-| Include a specific event type.
 | 
			
		||||
-}
 | 
			
		||||
withEventType : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withEventType x (RoomEventFilter ({ types } as data)) =
 | 
			
		||||
    RoomEventFilter { data | types = SF.with x types }
 | 
			
		||||
 | 
			
		||||
{-| Include all event types that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withOtherEventTypes : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withOtherEventTypes (RoomEventFilter ({ types } as data)) =
 | 
			
		||||
    RoomEventFilter { data | types = SF.withOthers types }
 | 
			
		||||
 | 
			
		||||
{-| Include all rooms that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withOtherRooms : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withOtherRooms (RoomEventFilter ({ rooms } as data)) =
 | 
			
		||||
    RoomEventFilter { data | rooms = SF.withOthers rooms }
 | 
			
		||||
 | 
			
		||||
{-| Include all senders that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withOtherSenders : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withOtherSenders (RoomEventFilter ({ senders } as data)) =
 | 
			
		||||
    RoomEventFilter { data | senders = SF.withOthers senders }
 | 
			
		||||
 | 
			
		||||
{-| Include a specific room.
 | 
			
		||||
-}
 | 
			
		||||
withRoom : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withRoom x (RoomEventFilter ({ rooms } as data)) =
 | 
			
		||||
    RoomEventFilter { data | rooms = SF.with x rooms }
 | 
			
		||||
 | 
			
		||||
{-| Include a specific sender.
 | 
			
		||||
-}
 | 
			
		||||
withSender : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withSender x (RoomEventFilter ({ senders } as data)) =
 | 
			
		||||
    RoomEventFilter { data | senders = SF.with x senders }
 | 
			
		||||
 | 
			
		||||
{-| Ignore a specific event type.
 | 
			
		||||
-}
 | 
			
		||||
withoutEventType : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutEventType x (RoomEventFilter ({ types } as data)) =
 | 
			
		||||
    RoomEventFilter { data | types = SF.without x types }
 | 
			
		||||
 | 
			
		||||
{-| Ignore all rooms that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withoutOtherEventTypes : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutOtherEventTypes (RoomEventFilter ({ types } as data)) =
 | 
			
		||||
    RoomEventFilter { data | types = SF.withoutOthers types }
 | 
			
		||||
 | 
			
		||||
{-| Ignore all rooms that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withoutOtherRooms : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutOtherRooms (RoomEventFilter ({ rooms } as data)) =
 | 
			
		||||
    RoomEventFilter { data | rooms = SF.withoutOthers rooms }
 | 
			
		||||
 | 
			
		||||
{-| Ignore all senders that haven't been explicitly mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withoutOtherSenders : RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutOtherSenders (RoomEventFilter ({ senders } as data)) =
 | 
			
		||||
    RoomEventFilter { data | senders = SF.withoutOthers senders }
 | 
			
		||||
 | 
			
		||||
{-| Ignore a specific room.
 | 
			
		||||
-}
 | 
			
		||||
withoutRoom : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutRoom x (RoomEventFilter ({ rooms } as data)) =
 | 
			
		||||
    RoomEventFilter { data | rooms = SF.without x rooms }
 | 
			
		||||
 | 
			
		||||
{-| Ignore a specific sender.
 | 
			
		||||
-}
 | 
			
		||||
withoutSender : String -> RoomEventFilter -> RoomEventFilter
 | 
			
		||||
withoutSender x (RoomEventFilter ({ senders } as data)) =
 | 
			
		||||
    RoomEventFilter { data | senders = SF.without x senders }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
module Internal.Tools.Filters.Main exposing (..)
 | 
			
		||||
{-| This module contains the main functions used to get, manipulate and change
 | 
			
		||||
filters according to their needs.
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Internal.Tools.Filters.Filter as F
 | 
			
		||||
import Internal.Tools.Filters.SimpleFilter as SF
 | 
			
		||||
 | 
			
		||||
type alias Filter = F.Filter
 | 
			
		||||
 | 
			
		||||
type alias SimpleFilter = SF.SimpleFilter String
 | 
			
		||||
 | 
			
		||||
{-| Filter that adds all occurrences by default, but leaves a few ones out.
 | 
			
		||||
 | 
			
		||||
When provided with an empty list, the filter allows all types.
 | 
			
		||||
-}
 | 
			
		||||
allExcept : List String -> SimpleFilter
 | 
			
		||||
allExcept =
 | 
			
		||||
    List.foldl SF.without SF.all
 | 
			
		||||
 | 
			
		||||
{-| Filter that removes everything by default, but leaves a few ones in.
 | 
			
		||||
 | 
			
		||||
When provided with an empty list, the filter allows nothing.
 | 
			
		||||
-}
 | 
			
		||||
only : List String -> SimpleFilter
 | 
			
		||||
only =
 | 
			
		||||
    List.foldl SF.with SF.none
 | 
			
		||||
 | 
			
		||||
fromSimpleFilter :
 | 
			
		||||
    { accountDataTypes : SimpleFilter
 | 
			
		||||
    , presence : { limit : Maybe Int, senders : SimpleFilter, types : SimpleFilter }
 | 
			
		||||
    , ephemeral : { limit : Maybe Int, senders : SimpleFilter, types : SimpleFilter }
 | 
			
		||||
    , roomIds : SimpleFilter
 | 
			
		||||
    , lazyLoadMembers : Bool
 | 
			
		||||
    , roomEvents : { limit : Maybe Int, senders : SimpleFilter, types : SimpleFilter }
 | 
			
		||||
    } -> Filter
 | 
			
		||||
fromSimpleFilter data =
 | 
			
		||||
    F.Filter
 | 
			
		||||
        { accountData =
 | 
			
		||||
            F.EventFilter
 | 
			
		||||
                { limit = Nothing
 | 
			
		||||
                , senders = SF.all
 | 
			
		||||
                , types = data.accountDataTypes
 | 
			
		||||
                }
 | 
			
		||||
        , presence =
 | 
			
		||||
            F.EventFilter
 | 
			
		||||
                { limit = data.presence.limit
 | 
			
		||||
                , senders = data.presence.senders
 | 
			
		||||
                , types = data.presence.types
 | 
			
		||||
                }
 | 
			
		||||
        , room =
 | 
			
		||||
            F.RoomFilter
 | 
			
		||||
                { accountData =
 | 
			
		||||
                    F.RoomEventFilter
 | 
			
		||||
                        { lazyLoadMembers = data.lazyLoadMembers
 | 
			
		||||
                        , limit = Nothing
 | 
			
		||||
                        , rooms = data.roomIds
 | 
			
		||||
                        , senders = SF.all
 | 
			
		||||
                        , types = data.accountDataTypes
 | 
			
		||||
                        }
 | 
			
		||||
                , ephemeral =
 | 
			
		||||
                    F.RoomEventFilter
 | 
			
		||||
                        { lazyLoadMembers = data.lazyLoadMembers
 | 
			
		||||
                        , limit = data.ephemeral.limit
 | 
			
		||||
                        , rooms = data.roomIds
 | 
			
		||||
                        , senders = data.ephemeral.senders
 | 
			
		||||
                        , types = data.ephemeral.types
 | 
			
		||||
                        }
 | 
			
		||||
                , rooms = data.roomIds
 | 
			
		||||
                , timeline = 
 | 
			
		||||
                    F.RoomEventFilter
 | 
			
		||||
                        { lazyLoadMembers = data.lazyLoadMembers
 | 
			
		||||
                        , limit = data.roomEvents.limit
 | 
			
		||||
                        , rooms = data.roomIds
 | 
			
		||||
                        , senders = data.roomEvents.senders
 | 
			
		||||
                        , types = data.roomEvents.types
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| Get the intersection of two filters.
 | 
			
		||||
-}
 | 
			
		||||
intersect : Filter -> Filter -> Filter
 | 
			
		||||
intersect =
 | 
			
		||||
    F.intersectFilter
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,172 @@
 | 
			
		|||
module Internal.Tools.Filters.SimpleFilter exposing (..)
 | 
			
		||||
{-| The SimpleFilter tracks values that should or should not be monitored.
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Dict exposing (Dict)
 | 
			
		||||
 | 
			
		||||
{-| SimpleFilter type that tracks items to include or exclude.
 | 
			
		||||
-}
 | 
			
		||||
type alias SimpleFilter a =
 | 
			
		||||
    { specificOnes : Dict a Bool
 | 
			
		||||
    , includeOthers : Bool
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
all : SimpleFilter a
 | 
			
		||||
all =
 | 
			
		||||
    { specificOnes = Dict.empty
 | 
			
		||||
    , includeOthers = True
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Use filter ones that are only available in the first filter.
 | 
			
		||||
-}
 | 
			
		||||
diff : SimpleFilter comparable -> SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
diff f1 f2 =
 | 
			
		||||
    { specificOnes =
 | 
			
		||||
        Dict.merge
 | 
			
		||||
            (\k v1 -> Dict.insert k (v1 && not f2.includeOthers))
 | 
			
		||||
            (\k v1 v2 -> Dict.insert k (v1 && not v2))
 | 
			
		||||
            (\k v2 -> Dict.insert k (f1.includeOthers && not v2))
 | 
			
		||||
            f1.specificOnes
 | 
			
		||||
            f2.specificOnes
 | 
			
		||||
            Dict.empty
 | 
			
		||||
    , includeOthers = f1.includeOthers && not f2.includeOthers
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Form a filter that only shows the values that two filters have in common.
 | 
			
		||||
-}
 | 
			
		||||
intersect : SimpleFilter comparable -> SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
intersect f1 f2 =
 | 
			
		||||
    { specificOnes =
 | 
			
		||||
        Dict.merge
 | 
			
		||||
            (\key v1 -> Dict.insert key (v1 && f2.includeOthers))
 | 
			
		||||
            (\key v1 v2 -> Dict.insert key (v1 && v2))
 | 
			
		||||
            (\key v2 -> Dict.insert key (f1.includeOthers && v2))
 | 
			
		||||
            f1.specificOnes
 | 
			
		||||
            f2.specificOnes
 | 
			
		||||
            Dict.empty
 | 
			
		||||
    , includeOthers = f1.includeOthers && f2.includeOthers
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Start with a filter that includes none.
 | 
			
		||||
-}
 | 
			
		||||
none : SimpleFilter a
 | 
			
		||||
none =
 | 
			
		||||
    { specificOnes = Dict.empty
 | 
			
		||||
    , includeOthers = False
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Check whether a SimpleFilter is a subset of another filter.
 | 
			
		||||
-}
 | 
			
		||||
subset : SimpleFilter comparable -> SimpleFilter comparable -> Bool
 | 
			
		||||
subset small large =
 | 
			
		||||
    if small.includeOthers && not large.includeOthers then
 | 
			
		||||
        False
 | 
			
		||||
    else
 | 
			
		||||
        -- All elements of small are in large
 | 
			
		||||
        Dict.merge
 | 
			
		||||
            (\_ s ->
 | 
			
		||||
                if s && not large.includeOthers then
 | 
			
		||||
                    always False
 | 
			
		||||
                else
 | 
			
		||||
                    identity
 | 
			
		||||
            )
 | 
			
		||||
            (\_ s l ->
 | 
			
		||||
                if s && not l then
 | 
			
		||||
                    always False
 | 
			
		||||
                else
 | 
			
		||||
                    identity
 | 
			
		||||
            )
 | 
			
		||||
            (\_ l ->
 | 
			
		||||
                if small.includeOthers && not l then
 | 
			
		||||
                    always False
 | 
			
		||||
                else
 | 
			
		||||
                    identity
 | 
			
		||||
            )
 | 
			
		||||
            small.specificOnes
 | 
			
		||||
            large.specificOnes
 | 
			
		||||
            True
 | 
			
		||||
 | 
			
		||||
{-| Encode a SimpleFilter into a list of items to exclude.
 | 
			
		||||
-}
 | 
			
		||||
toExclude : SimpleFilter comparable -> Maybe (List comparable)
 | 
			
		||||
toExclude f =
 | 
			
		||||
    f.specificOnes
 | 
			
		||||
        |> Dict.filter (always not)
 | 
			
		||||
        |> Dict.keys
 | 
			
		||||
        |> Just
 | 
			
		||||
 | 
			
		||||
{-| Encode a SimpleFilter into a list of items to include.
 | 
			
		||||
-}
 | 
			
		||||
toInclude : SimpleFilter comparable -> Maybe (List comparable)
 | 
			
		||||
toInclude f =
 | 
			
		||||
    if f.includeOthers then
 | 
			
		||||
        Nothing
 | 
			
		||||
    else
 | 
			
		||||
        f.specificOnes
 | 
			
		||||
            |> Dict.filter (always identity)
 | 
			
		||||
            |> Dict.keys
 | 
			
		||||
            |> Just
 | 
			
		||||
 | 
			
		||||
{-| Create a SimpleFilter out of two optionally present lists.
 | 
			
		||||
-}
 | 
			
		||||
toSimpleFilter : Maybe (List comparable) -> Maybe (List comparable) -> SimpleFilter comparable
 | 
			
		||||
toSimpleFilter these notThese =
 | 
			
		||||
    let
 | 
			
		||||
        no : List comparable
 | 
			
		||||
        no = Maybe.withDefault [] notThese
 | 
			
		||||
    in
 | 
			
		||||
    case these of
 | 
			
		||||
        Just yes ->
 | 
			
		||||
            { specificOnes =
 | 
			
		||||
                Dict.union
 | 
			
		||||
                    (Dict.fromList ( List.map (\x -> Tuple.pair x False) no ))
 | 
			
		||||
                    (Dict.fromList ( List.map (\x -> Tuple.pair x True) yes ))
 | 
			
		||||
            , includeOthers = False
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        Nothing ->
 | 
			
		||||
            { specificOnes =
 | 
			
		||||
                no
 | 
			
		||||
                    |> List.map (\x -> Tuple.pair x False)
 | 
			
		||||
                    |> Dict.fromList
 | 
			
		||||
            , includeOthers = True
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
{-| Form a filter that includes values if it is included in either filters.
 | 
			
		||||
-}
 | 
			
		||||
union : SimpleFilter comparable -> SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
union f1 f2 =
 | 
			
		||||
    { specificOnes =
 | 
			
		||||
        Dict.merge
 | 
			
		||||
            (\key v1 -> Dict.insert key (v1 || f2.includeOthers))
 | 
			
		||||
            (\key v1 v2 -> Dict.insert key (v1 || v2))
 | 
			
		||||
            (\key v2 -> Dict.insert key (f1.includeOthers || v2))
 | 
			
		||||
            f1.specificOnes
 | 
			
		||||
            f2.specificOnes
 | 
			
		||||
            Dict.empty
 | 
			
		||||
    , includeOthers = f1.includeOthers && f2.includeOthers
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
{-| Add a value that should be included.
 | 
			
		||||
-}
 | 
			
		||||
with : comparable -> SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
with x f =
 | 
			
		||||
    { f | specificOnes = Dict.insert x True f.specificOnes }
 | 
			
		||||
 | 
			
		||||
{-| Include all values that haven't been mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withOthers : SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
withOthers f =
 | 
			
		||||
    { f | includeOthers = True }
 | 
			
		||||
 | 
			
		||||
{-| Add a value that should be ignored.
 | 
			
		||||
-}
 | 
			
		||||
without : comparable -> SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
without x f =
 | 
			
		||||
    { f | specificOnes = Dict.insert x False f.specificOnes }
 | 
			
		||||
 | 
			
		||||
{-| Ignore all values that haven't been mentioned.
 | 
			
		||||
-}
 | 
			
		||||
withoutOthers : SimpleFilter comparable -> SimpleFilter comparable
 | 
			
		||||
withoutOthers f =
 | 
			
		||||
    { f | includeOthers = False }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,246 @@
 | 
			
		|||
module Internal.Tools.Filters.SpecObjects exposing
 | 
			
		||||
    ( EventFilter
 | 
			
		||||
    , Filter
 | 
			
		||||
    , RoomEventFilter
 | 
			
		||||
    , RoomFilter
 | 
			
		||||
    , StateFilter
 | 
			
		||||
    , encodeEventFilter
 | 
			
		||||
    , encodeFilter
 | 
			
		||||
    , encodeRoomEventFilter
 | 
			
		||||
    , encodeRoomFilter
 | 
			
		||||
    , encodeStateFilter
 | 
			
		||||
    , eventFilterDecoder
 | 
			
		||||
    , filterDecoder
 | 
			
		||||
    , roomEventFilterDecoder
 | 
			
		||||
    , roomFilterDecoder
 | 
			
		||||
    , stateFilterDecoder
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
{-| Automatically generated 'SpecObjects'
 | 
			
		||||
 | 
			
		||||
Last generated at Unix time 1681915222
 | 
			
		||||
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Internal.Tools.DecodeExtra as D exposing (opField, opFieldWithDefault)
 | 
			
		||||
import Internal.Tools.EncodeExtra exposing (maybeObject)
 | 
			
		||||
import Internal.Tools.SpecEnums as Enums
 | 
			
		||||
import Json.Decode as D
 | 
			
		||||
import Json.Encode as E
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Filter that describes which events to include/exclude.
 | 
			
		||||
-}
 | 
			
		||||
type alias EventFilter =
 | 
			
		||||
    { limit : Maybe Int
 | 
			
		||||
    , notSenders : Maybe (List String)
 | 
			
		||||
    , notTypes : Maybe (List String)
 | 
			
		||||
    , senders : Maybe (List String)
 | 
			
		||||
    , types : Maybe (List String)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
encodeEventFilter : EventFilter -> E.Value
 | 
			
		||||
encodeEventFilter data =
 | 
			
		||||
    maybeObject
 | 
			
		||||
        [ ( "limit", Maybe.map E.int data.limit )
 | 
			
		||||
        , ( "not_senders", Maybe.map (E.list E.string) data.notSenders )
 | 
			
		||||
        , ( "not_types", Maybe.map (E.list E.string) data.notTypes )
 | 
			
		||||
        , ( "senders", Maybe.map (E.list E.string) data.senders )
 | 
			
		||||
        , ( "types", Maybe.map (E.list E.string) data.types )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
eventFilterDecoder : D.Decoder EventFilter
 | 
			
		||||
eventFilterDecoder =
 | 
			
		||||
    D.map5
 | 
			
		||||
        (\a b c d e ->
 | 
			
		||||
            { limit = a, notSenders = b, notTypes = c, senders = d, types = e }
 | 
			
		||||
        )
 | 
			
		||||
        (opField "limit" D.int)
 | 
			
		||||
        (opField "not_senders" (D.list D.string))
 | 
			
		||||
        (opField "not_types" (D.list D.string))
 | 
			
		||||
        (opField "senders" (D.list D.string))
 | 
			
		||||
        (opField "types" (D.list D.string))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Main filter for filtering results
 | 
			
		||||
-}
 | 
			
		||||
type alias Filter =
 | 
			
		||||
    { accountData : Maybe EventFilter
 | 
			
		||||
    , eventFields : Maybe (List String)
 | 
			
		||||
    , eventFormat : Enums.EventFormat
 | 
			
		||||
    , presence : Maybe EventFilter
 | 
			
		||||
    , room : Maybe RoomFilter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
encodeFilter : Filter -> E.Value
 | 
			
		||||
encodeFilter data =
 | 
			
		||||
    maybeObject
 | 
			
		||||
        [ ( "account_data", Maybe.map encodeEventFilter data.accountData )
 | 
			
		||||
        , ( "event_fields", Maybe.map (E.list E.string) data.eventFields )
 | 
			
		||||
        , ( "event_format", Just <| Enums.encodeEventFormat data.eventFormat )
 | 
			
		||||
        , ( "presence", Maybe.map encodeEventFilter data.presence )
 | 
			
		||||
        , ( "room", Maybe.map encodeRoomFilter data.room )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
filterDecoder : D.Decoder Filter
 | 
			
		||||
filterDecoder =
 | 
			
		||||
    D.map5
 | 
			
		||||
        (\a b c d e ->
 | 
			
		||||
            { accountData = a, eventFields = b, eventFormat = c, presence = d, room = e }
 | 
			
		||||
        )
 | 
			
		||||
        (opField "account_data" eventFilterDecoder)
 | 
			
		||||
        (opField "event_fields" (D.list D.string))
 | 
			
		||||
        (opFieldWithDefault "event_format" Enums.Client Enums.eventFormatDecoder)
 | 
			
		||||
        (opField "presence" eventFilterDecoder)
 | 
			
		||||
        (opField "room" roomFilterDecoder)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Filter that describes which events to include/exclude in a Matrix room.
 | 
			
		||||
-}
 | 
			
		||||
type alias RoomEventFilter =
 | 
			
		||||
    { containsUrl : Maybe Bool
 | 
			
		||||
    , includeRedundantMembers : Bool
 | 
			
		||||
    , lazyLoadMembers : Bool
 | 
			
		||||
    , limit : Maybe Int
 | 
			
		||||
    , notRooms : Maybe (List String)
 | 
			
		||||
    , notSenders : Maybe (List String)
 | 
			
		||||
    , notTypes : Maybe (List String)
 | 
			
		||||
    , rooms : Maybe (List String)
 | 
			
		||||
    , senders : Maybe (List String)
 | 
			
		||||
    , types : Maybe (List String)
 | 
			
		||||
    , unreadThreadNotifications : Bool
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
encodeRoomEventFilter : RoomEventFilter -> E.Value
 | 
			
		||||
encodeRoomEventFilter data =
 | 
			
		||||
    maybeObject
 | 
			
		||||
        [ ( "contains_url", Maybe.map E.bool data.containsUrl )
 | 
			
		||||
        , ( "include_redundant_members", Just <| E.bool data.includeRedundantMembers )
 | 
			
		||||
        , ( "lazy_load_members", Just <| E.bool data.lazyLoadMembers )
 | 
			
		||||
        , ( "limit", Maybe.map E.int data.limit )
 | 
			
		||||
        , ( "not_rooms", Maybe.map (E.list E.string) data.notRooms )
 | 
			
		||||
        , ( "not_senders", Maybe.map (E.list E.string) data.notSenders )
 | 
			
		||||
        , ( "not_types", Maybe.map (E.list E.string) data.notTypes )
 | 
			
		||||
        , ( "rooms", Maybe.map (E.list E.string) data.rooms )
 | 
			
		||||
        , ( "senders", Maybe.map (E.list E.string) data.senders )
 | 
			
		||||
        , ( "types", Maybe.map (E.list E.string) data.types )
 | 
			
		||||
        , ( "unread_thread_notifications", Just <| E.bool data.unreadThreadNotifications )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
roomEventFilterDecoder : D.Decoder RoomEventFilter
 | 
			
		||||
roomEventFilterDecoder =
 | 
			
		||||
    D.map11
 | 
			
		||||
        (\a b c d e f g h i j k ->
 | 
			
		||||
            { containsUrl = a, includeRedundantMembers = b, lazyLoadMembers = c, limit = d, notRooms = e, notSenders = f, notTypes = g, rooms = h, senders = i, types = j, unreadThreadNotifications = k }
 | 
			
		||||
        )
 | 
			
		||||
        (opField "contains_url" D.bool)
 | 
			
		||||
        (opFieldWithDefault "include_redundant_members" False D.bool)
 | 
			
		||||
        (opFieldWithDefault "lazy_load_members" False D.bool)
 | 
			
		||||
        (opField "limit" D.int)
 | 
			
		||||
        (opField "not_rooms" (D.list D.string))
 | 
			
		||||
        (opField "not_senders" (D.list D.string))
 | 
			
		||||
        (opField "not_types" (D.list D.string))
 | 
			
		||||
        (opField "rooms" (D.list D.string))
 | 
			
		||||
        (opField "senders" (D.list D.string))
 | 
			
		||||
        (opField "types" (D.list D.string))
 | 
			
		||||
        (opFieldWithDefault "unread_thread_notifications" False D.bool)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Filter that describes what should and shouldn't be included for rooms.
 | 
			
		||||
-}
 | 
			
		||||
type alias RoomFilter =
 | 
			
		||||
    { accountData : Maybe RoomEventFilter
 | 
			
		||||
    , ephemeral : Maybe RoomEventFilter
 | 
			
		||||
    , includeLeave : Bool
 | 
			
		||||
    , notRooms : Maybe (List String)
 | 
			
		||||
    , rooms : Maybe (List String)
 | 
			
		||||
    , state : Maybe StateFilter
 | 
			
		||||
    , timeline : Maybe RoomEventFilter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
encodeRoomFilter : RoomFilter -> E.Value
 | 
			
		||||
encodeRoomFilter data =
 | 
			
		||||
    maybeObject
 | 
			
		||||
        [ ( "account_data", Maybe.map encodeRoomEventFilter data.accountData )
 | 
			
		||||
        , ( "ephemeral", Maybe.map encodeRoomEventFilter data.ephemeral )
 | 
			
		||||
        , ( "include_leave", Just <| E.bool data.includeLeave )
 | 
			
		||||
        , ( "not_rooms", Maybe.map (E.list E.string) data.notRooms )
 | 
			
		||||
        , ( "rooms", Maybe.map (E.list E.string) data.rooms )
 | 
			
		||||
        , ( "state", Maybe.map encodeStateFilter data.state )
 | 
			
		||||
        , ( "timeline", Maybe.map encodeRoomEventFilter data.timeline )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
roomFilterDecoder : D.Decoder RoomFilter
 | 
			
		||||
roomFilterDecoder =
 | 
			
		||||
    D.map7
 | 
			
		||||
        (\a b c d e f g ->
 | 
			
		||||
            { accountData = a, ephemeral = b, includeLeave = c, notRooms = d, rooms = e, state = f, timeline = g }
 | 
			
		||||
        )
 | 
			
		||||
        (opField "account_data" roomEventFilterDecoder)
 | 
			
		||||
        (opField "ephemeral" roomEventFilterDecoder)
 | 
			
		||||
        (opFieldWithDefault "include_leave" False D.bool)
 | 
			
		||||
        (opField "not_rooms" (D.list D.string))
 | 
			
		||||
        (opField "rooms" (D.list D.string))
 | 
			
		||||
        (opField "state" stateFilterDecoder)
 | 
			
		||||
        (opField "timeline" roomEventFilterDecoder)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Filter that describes which events to include/exclude in a Matrix room.
 | 
			
		||||
-}
 | 
			
		||||
type alias StateFilter =
 | 
			
		||||
    { containsUrl : Maybe Bool
 | 
			
		||||
    , includeRedundantMembers : Bool
 | 
			
		||||
    , lazyLoadMembers : Bool
 | 
			
		||||
    , limit : Maybe Int
 | 
			
		||||
    , notRooms : Maybe (List String)
 | 
			
		||||
    , notSenders : Maybe (List String)
 | 
			
		||||
    , notTypes : Maybe (List String)
 | 
			
		||||
    , rooms : Maybe (List String)
 | 
			
		||||
    , senders : Maybe (List String)
 | 
			
		||||
    , types : Maybe (List String)
 | 
			
		||||
    , unreadThreadNotifications : Bool
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
encodeStateFilter : StateFilter -> E.Value
 | 
			
		||||
encodeStateFilter data =
 | 
			
		||||
    maybeObject
 | 
			
		||||
        [ ( "contains_url", Maybe.map E.bool data.containsUrl )
 | 
			
		||||
        , ( "include_redundant_members", Just <| E.bool data.includeRedundantMembers )
 | 
			
		||||
        , ( "lazy_load_members", Just <| E.bool data.lazyLoadMembers )
 | 
			
		||||
        , ( "limit", Maybe.map E.int data.limit )
 | 
			
		||||
        , ( "not_rooms", Maybe.map (E.list E.string) data.notRooms )
 | 
			
		||||
        , ( "not_senders", Maybe.map (E.list E.string) data.notSenders )
 | 
			
		||||
        , ( "not_types", Maybe.map (E.list E.string) data.notTypes )
 | 
			
		||||
        , ( "rooms", Maybe.map (E.list E.string) data.rooms )
 | 
			
		||||
        , ( "senders", Maybe.map (E.list E.string) data.senders )
 | 
			
		||||
        , ( "types", Maybe.map (E.list E.string) data.types )
 | 
			
		||||
        , ( "unread_thread_notifications", Just <| E.bool data.unreadThreadNotifications )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
stateFilterDecoder : D.Decoder StateFilter
 | 
			
		||||
stateFilterDecoder =
 | 
			
		||||
    D.map11
 | 
			
		||||
        (\a b c d e f g h i j k ->
 | 
			
		||||
            { containsUrl = a, includeRedundantMembers = b, lazyLoadMembers = c, limit = d, notRooms = e, notSenders = f, notTypes = g, rooms = h, senders = i, types = j, unreadThreadNotifications = k }
 | 
			
		||||
        )
 | 
			
		||||
        (opField "contains_url" D.bool)
 | 
			
		||||
        (opFieldWithDefault "include_redundant_members" False D.bool)
 | 
			
		||||
        (opFieldWithDefault "lazy_load_members" False D.bool)
 | 
			
		||||
        (opField "limit" D.int)
 | 
			
		||||
        (opField "not_rooms" (D.list D.string))
 | 
			
		||||
        (opField "not_senders" (D.list D.string))
 | 
			
		||||
        (opField "not_types" (D.list D.string))
 | 
			
		||||
        (opField "rooms" (D.list D.string))
 | 
			
		||||
        (opField "senders" (D.list D.string))
 | 
			
		||||
        (opField "types" (D.list D.string))
 | 
			
		||||
        (opFieldWithDefault "unread_thread_notifications" False D.bool)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,135 @@
 | 
			
		|||
version: v1
 | 
			
		||||
name: SpecObjects
 | 
			
		||||
objects:
 | 
			
		||||
  Filter:
 | 
			
		||||
    description: Main filter for filtering results
 | 
			
		||||
    fields:
 | 
			
		||||
      account_data:
 | 
			
		||||
        type: EventFilter
 | 
			
		||||
        required: false
 | 
			
		||||
      event_fields:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      event_format:
 | 
			
		||||
        type: Enums.EventFormat
 | 
			
		||||
        default: Enums.Client
 | 
			
		||||
      presence:
 | 
			
		||||
        type: EventFilter
 | 
			
		||||
        required: false
 | 
			
		||||
      room:
 | 
			
		||||
        type: RoomFilter
 | 
			
		||||
        required: false
 | 
			
		||||
  EventFilter:
 | 
			
		||||
    description: Filter that describes which events to include/exclude.
 | 
			
		||||
    fields:
 | 
			
		||||
      limit:
 | 
			
		||||
        type: int
 | 
			
		||||
        required: false
 | 
			
		||||
      not_senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      not_types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
  RoomFilter:
 | 
			
		||||
    description: Filter that describes what should and shouldn't be included for rooms.
 | 
			
		||||
    fields:
 | 
			
		||||
      account_data:
 | 
			
		||||
        type: RoomEventFilter
 | 
			
		||||
        required: false
 | 
			
		||||
      ephemeral:
 | 
			
		||||
        type: RoomEventFilter
 | 
			
		||||
        required: false
 | 
			
		||||
      include_leave:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
      not_rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      state:
 | 
			
		||||
        type: StateFilter
 | 
			
		||||
        required: false
 | 
			
		||||
      timeline:
 | 
			
		||||
        type: RoomEventFilter
 | 
			
		||||
        required: false
 | 
			
		||||
  RoomEventFilter:
 | 
			
		||||
    description: Filter that describes which events to include/exclude in a Matrix room.
 | 
			
		||||
    fields:
 | 
			
		||||
      contains_url:
 | 
			
		||||
        type: bool
 | 
			
		||||
        required: false
 | 
			
		||||
      include_redundant_members:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
      lazy_load_members:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
      limit:
 | 
			
		||||
        type: int
 | 
			
		||||
        required: false
 | 
			
		||||
      not_rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      not_senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      not_types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      unread_thread_notifications:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
  StateFilter:
 | 
			
		||||
    description: Filter that describes which events to include/exclude in a Matrix room.
 | 
			
		||||
    fields:
 | 
			
		||||
      contains_url:
 | 
			
		||||
        type: bool
 | 
			
		||||
        required: false
 | 
			
		||||
      include_redundant_members:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
      lazy_load_members:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
      limit:
 | 
			
		||||
        type: int
 | 
			
		||||
        required: false
 | 
			
		||||
      not_rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      not_senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      not_types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      rooms:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      senders:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      types:
 | 
			
		||||
        type: '[string]'
 | 
			
		||||
        required: false
 | 
			
		||||
      unread_thread_notifications:
 | 
			
		||||
        type: bool
 | 
			
		||||
        default: 'False'
 | 
			
		||||
| 
						 | 
				
			
			@ -6,8 +6,10 @@ For example, this is used to store events by their event id, or store rooms by t
 | 
			
		|||
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Dict exposing (Dict)
 | 
			
		||||
 | 
			
		||||
import FastDict as Dict exposing (Dict)
 | 
			
		||||
import Json.Decode as D
 | 
			
		||||
import Json.Encode as E
 | 
			
		||||
import Hash exposing (Hash)
 | 
			
		||||
 | 
			
		||||
type Hashdict a
 | 
			
		||||
    = Hashdict
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +22,12 @@ empty : (a -> String) -> Hashdict a
 | 
			
		|||
empty hash =
 | 
			
		||||
    Hashdict { hash = hash, values = Dict.empty }
 | 
			
		||||
 | 
			
		||||
encode : Hashdict E.Value -> E.Value
 | 
			
		||||
encode (Hashdict h) =
 | 
			
		||||
    h.values
 | 
			
		||||
        |> Dict.toList
 | 
			
		||||
        |> E.object
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fromList : (a -> String) -> List a -> Hashdict a
 | 
			
		||||
fromList hash xs =
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +54,9 @@ keys : Hashdict a -> List String
 | 
			
		|||
keys (Hashdict h) =
 | 
			
		||||
    Dict.keys h.values
 | 
			
		||||
 | 
			
		||||
toList : Hashdict a -> List (String, a)
 | 
			
		||||
toList (Hashdict h) =
 | 
			
		||||
    Dict.toList h.values
 | 
			
		||||
 | 
			
		||||
union : Hashdict a -> Hashdict a -> Hashdict a
 | 
			
		||||
union (Hashdict h1) (Hashdict h2) =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
module Internal.Tools.Iddict exposing (..)
 | 
			
		||||
{-| The id-dict stores values and gives them a unique id.
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import FastDict as Dict exposing (Dict)
 | 
			
		||||
 | 
			
		||||
type Iddict a
 | 
			
		||||
    = Iddict 
 | 
			
		||||
        { cursor : Int
 | 
			
		||||
        , dict : Dict Int a
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
empty : Iddict a
 | 
			
		||||
empty =
 | 
			
		||||
    Iddict
 | 
			
		||||
        { cursor = 0
 | 
			
		||||
        , dict = Dict.empty
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
get : Int -> Iddict a -> Maybe a
 | 
			
		||||
get k (Iddict { dict }) =
 | 
			
		||||
    Dict.get k dict
 | 
			
		||||
 | 
			
		||||
insert : a -> Iddict a -> (Int, Iddict a)
 | 
			
		||||
insert v (Iddict d) =
 | 
			
		||||
    ( d.cursor
 | 
			
		||||
    , Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
keys : Iddict a -> List Int
 | 
			
		||||
keys (Iddict { dict }) =
 | 
			
		||||
    Dict.keys dict
 | 
			
		||||
 | 
			
		||||
remove : Int -> Iddict a -> Iddict a
 | 
			
		||||
remove k (Iddict d) =
 | 
			
		||||
    Iddict { d | dict = Dict.remove k d.dict }
 | 
			
		||||
 | 
			
		||||
values : Iddict a -> List a
 | 
			
		||||
values (Iddict { dict }) =
 | 
			
		||||
    Dict.values dict
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
module Internal.Tools.VaultResult exposing (..)
 | 
			
		||||
 | 
			
		||||
import Internal.Tools.Exceptions as X
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Info b a
 | 
			
		||||
    = Info a
 | 
			
		||||
    | NoInfo
 | 
			
		||||
    | InfoFailed { status : LoadingError, retry : Task X.Error b }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type LoadingError
 | 
			
		||||
    = NeverRequested
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +1,113 @@
 | 
			
		|||
module Internal.Values.Timeline exposing (..)
 | 
			
		||||
 | 
			
		||||
{-| This module shapes the Timeline type used to keep track of timelines in Matrix rooms.
 | 
			
		||||
{-| The Timeline can be very complex, and it can be represented in surprisingly
 | 
			
		||||
complex manners. This module aims to provide one single Timeline type that
 | 
			
		||||
accepts the complex pieces of information from the API and contain it all in
 | 
			
		||||
a simple way to view events.
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import FastDict as Dict exposing (Dict)
 | 
			
		||||
import Internal.Config.Leaking as Leaking
 | 
			
		||||
import Internal.Tools.Fold as Fold
 | 
			
		||||
import Internal.Values.Event as Event exposing (IEvent)
 | 
			
		||||
import Internal.Values.StateManager as StateManager exposing (StateManager)
 | 
			
		||||
import Internal.Tools.DefaultDict as DefaultDict exposing (DefaultDict)
 | 
			
		||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
 | 
			
		||||
import Internal.Tools.Iddict as Iddict exposing (Iddict)
 | 
			
		||||
import Internal.Tools.Filters.Main as Filter exposing (Filter)
 | 
			
		||||
import Internal.Config.Leaking exposing (nextBatch)
 | 
			
		||||
 | 
			
		||||
{-| The Timeline is a comprehensive object describing a timeline in a room.
 | 
			
		||||
 | 
			
		||||
Any Timeline type contains the following pieces of information:
 | 
			
		||||
 | 
			
		||||
- `events` Comprehensive dictionary containing all locally stored timeline events
 | 
			
		||||
- `batches` Comprehensive dictionary containing all batches. Batches are pieces 
 | 
			
		||||
    of the timeline that have been sent by the homeserver.
 | 
			
		||||
- `token` Dictionary that maps for each batch token which batches it borders
 | 
			
		||||
- `mostRecentSync` Id of the most "recent" batch in the timeline
 | 
			
		||||
-}
 | 
			
		||||
type Timeline
 | 
			
		||||
    = Timeline
 | 
			
		||||
        { prevBatch : String
 | 
			
		||||
        , nextBatch : String
 | 
			
		||||
        , events : List IEvent
 | 
			
		||||
        , stateAtStart : StateManager
 | 
			
		||||
        , previous : BeforeTimeline
 | 
			
		||||
        { events : Hashdict IEvent
 | 
			
		||||
        , batches : Iddict TimelineBatch
 | 
			
		||||
        , token : DefaultDict String (List Int)
 | 
			
		||||
        , mostRecentSync : Maybe Int
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
{-| A BatchToken is a token that has been handed out by the server to mark the end of a  -}
 | 
			
		||||
type alias BatchToken = String
 | 
			
		||||
 | 
			
		||||
type alias TimelineBatch =
 | 
			
		||||
    { prevBatch : List Batch
 | 
			
		||||
    , nextBatch : List Batch
 | 
			
		||||
    , filter : Filter
 | 
			
		||||
    , events : List String
 | 
			
		||||
    , stateDelta : StateManager
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
type Batch
 | 
			
		||||
    = Token BatchToken
 | 
			
		||||
    | Batch Int
 | 
			
		||||
 | 
			
		||||
addNewSync :
 | 
			
		||||
    { events : List IEvent
 | 
			
		||||
    , filter : Filter
 | 
			
		||||
    , limited : Bool
 | 
			
		||||
    , nextBatch : String
 | 
			
		||||
    , prevBatch : String
 | 
			
		||||
    , stateDelta : Maybe StateManager
 | 
			
		||||
    } -> Timeline -> Timeline
 | 
			
		||||
addNewSync data (Timeline timeline) =
 | 
			
		||||
    let
 | 
			
		||||
        batchToInsert : TimelineBatch
 | 
			
		||||
        batchToInsert =
 | 
			
		||||
            { prevBatch = 
 | 
			
		||||
                [ Just <| Token data.prevBatch
 | 
			
		||||
                , Maybe.map Batch timeline.mostRecentSync
 | 
			
		||||
                ]
 | 
			
		||||
                    |> List.filterMap identity
 | 
			
		||||
            , nextBatch =
 | 
			
		||||
                [ Token data.nextBatch ]
 | 
			
		||||
            , filter = data.filter
 | 
			
		||||
            , events = List.map Event.eventId data.events
 | 
			
		||||
            , stateDelta = Maybe.withDefault StateManager.empty data.stateDelta
 | 
			
		||||
            }
 | 
			
		||||
    in
 | 
			
		||||
        case Iddict.insert batchToInsert timeline.batches of
 | 
			
		||||
            ( batchId, batches ) ->
 | 
			
		||||
                Timeline
 | 
			
		||||
                    { events = List.foldl Hashdict.insert timeline.events data.events
 | 
			
		||||
                    , batches = batches
 | 
			
		||||
                    , mostRecentSync = Just batchId
 | 
			
		||||
                    , token =
 | 
			
		||||
                        timeline.token
 | 
			
		||||
                            |> DefaultDict.update data.prevBatch
 | 
			
		||||
                                (\value ->
 | 
			
		||||
                                    case value of
 | 
			
		||||
                                        Just v ->
 | 
			
		||||
                                            Just (batchId :: v)
 | 
			
		||||
                                        Nothing ->
 | 
			
		||||
                                            Just [ batchId ]
 | 
			
		||||
                                )
 | 
			
		||||
                            |> DefaultDict.update data.nextBatch
 | 
			
		||||
                                (\value ->
 | 
			
		||||
                                    case value of
 | 
			
		||||
                                        Just v ->
 | 
			
		||||
                                            Just (batchId :: v)
 | 
			
		||||
                                        Nothing ->
 | 
			
		||||
                                            Just [ batchId ]
 | 
			
		||||
                                )
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
-- type Timeline
 | 
			
		||||
--     = Timeline
 | 
			
		||||
--         { prevBatch : String
 | 
			
		||||
--         , nextBatch : String
 | 
			
		||||
--         , events : List IEvent
 | 
			
		||||
--         , stateAtStart : StateManager
 | 
			
		||||
--         , previous : BeforeTimeline
 | 
			
		||||
--         }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type BeforeTimeline
 | 
			
		||||
    = Endless String
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -432,3 +432,8 @@ sync vault onResponse =
 | 
			
		|||
rooms : Vault -> List Room.Room
 | 
			
		||||
rooms =
 | 
			
		||||
    Snackbar.mapList Internal.getRooms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
settings : (Snackbar.Settings -> Snackbar.Settings) -> Vault -> Vault
 | 
			
		||||
settings =
 | 
			
		||||
    Snackbar.updateSettings
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,10 +33,8 @@ interact with the API.
 | 
			
		|||
import Internal.Api.VaultUpdate as Api
 | 
			
		||||
import Internal.Invite exposing (RoomInvite)
 | 
			
		||||
import Internal.Room exposing (Room)
 | 
			
		||||
import Internal.Tools.Exceptions as X
 | 
			
		||||
import Internal.Vault
 | 
			
		||||
import Json.Encode as E
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| The Matrix API requires you to keep track of a lot of tokens, keys, values and more.
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +56,13 @@ translate those instructions to a `VaultUpdate` that you can feed to your `Vault
 | 
			
		|||
type alias VaultUpdate =
 | 
			
		||||
    Api.VaultUpdate
 | 
			
		||||
 | 
			
		||||
{-| After evaluating an update, the `Status` type tells you how the Vault.
 | 
			
		||||
-}
 | 
			
		||||
type Status
 | 
			
		||||
    = Good
 | 
			
		||||
    | Warning String ((VaultUpdate -> msg) -> Cmd msg)
 | 
			
		||||
    | Break String ((VaultUpdate -> msg) -> Cmd msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| Create a new vault based on an access token.
 | 
			
		||||
Keep in mind that access tokens might eventually be revoked or expire,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,9 +68,7 @@ allowed for every room admin.
 | 
			
		|||
import Internal.Api.VaultUpdate exposing (VaultUpdate)
 | 
			
		||||
import Internal.Event as Event
 | 
			
		||||
import Internal.Room as Internal
 | 
			
		||||
import Internal.Tools.Exceptions as X
 | 
			
		||||
import Json.Decode as D
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| A room represents a channel of communication within a Matrix home server.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,10 +32,8 @@ Once you have the event you want, you can explore it with the following function
 | 
			
		|||
 | 
			
		||||
import Internal.Api.VaultUpdate exposing (VaultUpdate)
 | 
			
		||||
import Internal.Invite as Internal
 | 
			
		||||
import Internal.Tools.Exceptions as X
 | 
			
		||||
import Internal.Values.RoomInvite as IR
 | 
			
		||||
import Json.Encode as E
 | 
			
		||||
import Task exposing (Task)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{-| The `RoomInvite` type serves as an invite to a given room.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
module Matrix.Settings exposing (..)
 | 
			
		||||
{-| There are a lot of settings that you can change!
 | 
			
		||||
 | 
			
		||||
These settings change how the Vault interacts with the Matrix API.
 | 
			
		||||
You can adjust these values for performance reasons, for customizability, benchmarking,
 | 
			
		||||
or maybe just because you like it. :)
 | 
			
		||||
 | 
			
		||||
It is common to set all settings in the `init` function, but you can adjust all settings on the fly.
 | 
			
		||||
-}
 | 
			
		||||
 | 
			
		||||
import Internal.Vault exposing (Vault)
 | 
			
		||||
 | 
			
		||||
{-| When your Matrix client synchronizes with the homeserver, the homeserver often
 | 
			
		||||
responds quite quickly, giving all the information that you need.
 | 
			
		||||
 | 
			
		||||
Sometimes, the homeserver has nothing new to report, and instead makes you wait for a response.
 | 
			
		||||
This is called long-polling, and it's the homeserver waiting for an update to give to you.
 | 
			
		||||
Long-polling is very useful!
 | 
			
		||||
 | 
			
		||||
This setting sets a limit on how long the long-polling should last. It is smart
 | 
			
		||||
to make this equal to the interval at which you run the `sync` function.
 | 
			
		||||
 | 
			
		||||
**Default:** 10 (seconds)
 | 
			
		||||
-}
 | 
			
		||||
syncTimeout : Int -> Vault -> Vault
 | 
			
		||||
syncTimeout timeout =
 | 
			
		||||
    Internal.Vault.settings \data -> { data | syncTimeout = timeout }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
# Design refactors to be made
 | 
			
		||||
 | 
			
		||||
The following refactors are to be made:
 | 
			
		||||
 | 
			
		||||
- We need an `Info` type that informs the user whether data exists, and if not, why it doesn't exist.
 | 
			
		||||
 | 
			
		||||
- We need a `Timeline` type that users can use to go through threads in the global room timeline.
 | 
			
		||||
		Loading…
	
		Reference in New Issue