directory: better search, allow both simplex:/ and simplex.chat links in description (#3546)

* directory: new commands

* better search

* search test

* return group links in simplex.chat domain, allow both simplex:/ and simplex.chat links in group description
This commit is contained in:
Evgeny Poberezkin 2023-12-18 10:41:08 +00:00 committed by GitHub
parent 6fa0001ea7
commit f0338a03d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 250 additions and 33 deletions

View file

@ -21,14 +21,18 @@ where
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Data.Attoparsec.Text (Parser) import Data.Attoparsec.Text (Parser)
import qualified Data.Attoparsec.Text as A import qualified Data.Attoparsec.Text as A
import Data.Functor (($>))
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Directory.Store import Directory.Store
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol (MsgContent (..)) import Simplex.Chat.Protocol (MsgContent (..))
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Util ((<$?>))
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.Either (fromRight) import Data.Either (fromRight)
@ -83,6 +87,10 @@ deriving instance Show (SDirectoryRole r)
data DirectoryCmdTag (r :: DirectoryRole) where data DirectoryCmdTag (r :: DirectoryRole) where
DCHelp_ :: DirectoryCmdTag 'DRUser DCHelp_ :: DirectoryCmdTag 'DRUser
DCSearchNext_ :: DirectoryCmdTag 'DRUser
DCAllGroups_ :: DirectoryCmdTag 'DRUser
DCRecentGroups_ :: DirectoryCmdTag 'DRUser
DCSubmitGroup_ :: DirectoryCmdTag 'DRUser
DCConfirmDuplicateGroup_ :: DirectoryCmdTag 'DRUser DCConfirmDuplicateGroup_ :: DirectoryCmdTag 'DRUser
DCListUserGroups_ :: DirectoryCmdTag 'DRUser DCListUserGroups_ :: DirectoryCmdTag 'DRUser
DCDeleteGroup_ :: DirectoryCmdTag 'DRUser DCDeleteGroup_ :: DirectoryCmdTag 'DRUser
@ -100,6 +108,10 @@ data ADirectoryCmdTag = forall r. ADCT (SDirectoryRole r) (DirectoryCmdTag r)
data DirectoryCmd (r :: DirectoryRole) where data DirectoryCmd (r :: DirectoryRole) where
DCHelp :: DirectoryCmd 'DRUser DCHelp :: DirectoryCmd 'DRUser
DCSearchGroup :: Text -> DirectoryCmd 'DRUser DCSearchGroup :: Text -> DirectoryCmd 'DRUser
DCSearchNext :: DirectoryCmd 'DRUser
DCAllGroups :: DirectoryCmd 'DRUser
DCRecentGroups :: DirectoryCmd 'DRUser
DCSubmitGroup :: ConnReqContact -> DirectoryCmd 'DRUser
DCConfirmDuplicateGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser DCConfirmDuplicateGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser
DCListUserGroups :: DirectoryCmd 'DRUser DCListUserGroups :: DirectoryCmd 'DRUser
DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser DCDeleteGroup :: UserGroupRegId -> GroupName -> DirectoryCmd 'DRUser
@ -120,7 +132,9 @@ deriving instance Show ADirectoryCmd
directoryCmdP :: Parser ADirectoryCmd directoryCmdP :: Parser ADirectoryCmd
directoryCmdP = directoryCmdP =
(A.char '/' *> cmdStrP) <|> (ADC SDRUser . DCSearchGroup <$> A.takeText) (A.char '/' *> cmdStrP)
<|> (A.char '.' $> ADC SDRUser DCSearchNext)
<|> (ADC SDRUser . DCSearchGroup <$> A.takeText)
where where
cmdStrP = cmdStrP =
(tagP >>= \(ADCT u t) -> ADC u <$> (cmdP t <|> pure (DCCommandError t))) (tagP >>= \(ADCT u t) -> ADC u <$> (cmdP t <|> pure (DCCommandError t)))
@ -128,6 +142,10 @@ directoryCmdP =
tagP = A.takeTill (== ' ') >>= \case tagP = A.takeTill (== ' ') >>= \case
"help" -> u DCHelp_ "help" -> u DCHelp_
"h" -> u DCHelp_ "h" -> u DCHelp_
"next" -> u DCSearchNext_
"all" -> u DCAllGroups_
"new" -> u DCRecentGroups_
"submit" -> u DCSubmitGroup_
"confirm" -> u DCConfirmDuplicateGroup_ "confirm" -> u DCConfirmDuplicateGroup_
"list" -> u DCListUserGroups_ "list" -> u DCListUserGroups_
"ls" -> u DCListUserGroups_ "ls" -> u DCListUserGroups_
@ -146,6 +164,10 @@ directoryCmdP =
cmdP :: DirectoryCmdTag r -> Parser (DirectoryCmd r) cmdP :: DirectoryCmdTag r -> Parser (DirectoryCmd r)
cmdP = \case cmdP = \case
DCHelp_ -> pure DCHelp DCHelp_ -> pure DCHelp
DCSearchNext_ -> pure DCSearchNext
DCAllGroups_ -> pure DCAllGroups
DCRecentGroups_ -> pure DCRecentGroups
DCSubmitGroup_ -> fmap DCSubmitGroup . strDecode . encodeUtf8 <$?> (A.takeWhile1 isSpace *> A.takeText)
DCConfirmDuplicateGroup_ -> gc DCConfirmDuplicateGroup DCConfirmDuplicateGroup_ -> gc DCConfirmDuplicateGroup
DCListUserGroups_ -> pure DCListUserGroups DCListUserGroups_ -> pure DCListUserGroups
DCDeleteGroup_ -> gc DCDeleteGroup DCDeleteGroup_ -> gc DCDeleteGroup

View file

@ -21,6 +21,7 @@ data DirectoryOpts = DirectoryOpts
superUsers :: [KnownContact], superUsers :: [KnownContact],
directoryLog :: Maybe FilePath, directoryLog :: Maybe FilePath,
serviceName :: String, serviceName :: String,
searchResults :: Int,
testing :: Bool testing :: Bool
} }
@ -54,6 +55,7 @@ directoryOpts appDir defaultDbFileName = do
superUsers, superUsers,
directoryLog, directoryLog,
serviceName, serviceName,
searchResults = 10,
testing = False testing = False
} }

View file

@ -0,0 +1,32 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
module Directory.Search where
import Data.List (sortOn)
import Data.Ord (Down (..))
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text (Text)
import Data.Time.Clock (UTCTime)
import Simplex.Chat.Types
data SearchRequest = SearchRequest
{ searchType :: SearchType,
searchTime :: UTCTime,
sentGroups :: Set GroupId
}
data SearchType = STAll | STRecent | STSearch Text
takeTop :: Int -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)]
takeTop n = take n . sortOn (Down . currentMembers . snd)
takeRecent :: Int -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)]
takeRecent n = take n . sortOn (Down . (\GroupInfo {createdAt} -> createdAt) . fst)
groupIds :: [(GroupInfo, GroupSummary)] -> Set GroupId
groupIds = S.fromList . map (\(GroupInfo {groupId}, _) -> groupId)
filterNotSent :: Set GroupId -> [(GroupInfo, GroupSummary)] -> [(GroupInfo, GroupSummary)]
filterNotSent sentGroups = filter (\(GroupInfo {groupId}, _) -> groupId `S.notMember` sentGroups)

View file

@ -17,16 +17,16 @@ import Control.Concurrent.Async
import Control.Concurrent.STM import Control.Concurrent.STM
import Control.Monad import Control.Monad
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Data.List (sortOn)
import Data.Maybe (fromMaybe, maybeToList) import Data.Maybe (fromMaybe, maybeToList)
import Data.Ord (Down(..)) import Data.Set (Set)
import qualified Data.Set as S import qualified Data.Set as S
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Clock (getCurrentTime) import Data.Time.Clock (diffUTCTime, getCurrentTime)
import Data.Time.LocalTime (getCurrentTimeZone) import Data.Time.LocalTime (getCurrentTimeZone)
import Directory.Events import Directory.Events
import Directory.Options import Directory.Options
import Directory.Search
import Directory.Store import Directory.Store
import Simplex.Chat.Bot import Simplex.Chat.Bot
import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Bot.KnownContacts
@ -36,8 +36,10 @@ import Simplex.Chat.Messages
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.Protocol (MsgContent (..)) import Simplex.Chat.Protocol (MsgContent (..))
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.View (serializeChatResponse) import Simplex.Chat.View (serializeChatResponse, simplexChatContact)
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Util (safeDecodeUtf8, tshow, ($>>=), (<$$>)) import Simplex.Messaging.Util (safeDecodeUtf8, tshow, ($>>=), (<$$>))
import System.Directory (getAppUserDataDirectory) import System.Directory (getAppUserDataDirectory)
@ -55,6 +57,15 @@ data GroupRolesStatus
| GRSBadRoles | GRSBadRoles
deriving (Eq) deriving (Eq)
data ServiceState = ServiceState
{ searchRequests :: TMap ContactId SearchRequest
}
newServiceState :: IO ServiceState
newServiceState = do
searchRequests <- atomically TM.empty
pure ServiceState {searchRequests}
welcomeGetOpts :: IO DirectoryOpts welcomeGetOpts :: IO DirectoryOpts
welcomeGetOpts = do welcomeGetOpts = do
appDir <- getAppUserDataDirectory "simplex" appDir <- getAppUserDataDirectory "simplex"
@ -65,8 +76,9 @@ welcomeGetOpts = do
pure opts pure opts
directoryService :: DirectoryStore -> DirectoryOpts -> User -> ChatController -> IO () directoryService :: DirectoryStore -> DirectoryOpts -> User -> ChatController -> IO ()
directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {userId} cc = do directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testing} user@User {userId} cc = do
initializeBotAddress' (not testing) cc initializeBotAddress' (not testing) cc
env <- newServiceState
race_ (forever $ void getLine) . forever $ do race_ (forever $ void getLine) . forever $ do
(_, _, resp) <- atomically . readTBQueue $ outputQ cc (_, _, resp) <- atomically . readTBQueue $ outputQ cc
forM_ (crDirectoryEvent resp) $ \case forM_ (crDirectoryEvent resp) $ \case
@ -84,7 +96,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
DEItemEditIgnored _ct -> pure () DEItemEditIgnored _ct -> pure ()
DEItemDeleteIgnored _ct -> pure () DEItemDeleteIgnored _ct -> pure ()
DEContactCommand ct ciId aCmd -> case aCmd of DEContactCommand ct ciId aCmd -> case aCmd of
ADC SDRUser cmd -> deUserCommand ct ciId cmd ADC SDRUser cmd -> deUserCommand env ct ciId cmd
ADC SDRSuperUser cmd -> deSuperUserCommand ct ciId cmd ADC SDRSuperUser cmd -> deSuperUserCommand ct ciId cmd
where where
withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId
@ -105,8 +117,11 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
T.unpack $ "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name." T.unpack $ "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name."
getGroups :: Text -> IO (Maybe [(GroupInfo, GroupSummary)]) getGroups :: Text -> IO (Maybe [(GroupInfo, GroupSummary)])
getGroups search = getGroups = getGroups_ . Just
sendChatCmd cc (APIListGroups userId Nothing $ Just $ T.unpack search) >>= \case
getGroups_ :: Maybe Text -> IO (Maybe [(GroupInfo, GroupSummary)])
getGroups_ search_ =
sendChatCmd cc (APIListGroups userId Nothing $ T.unpack <$> search_) >>= \case
CRGroupsList {groups} -> pure $ Just groups CRGroupsList {groups} -> pure $ Just groups
_ -> pure Nothing _ -> pure Nothing
@ -140,7 +155,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
sendMessage cc ct $ sendMessage cc ct $
"Welcome to " <> serviceName <> " service!\n\ "Welcome to " <> serviceName <> " service!\n\
\Send a search string to find groups or */help* to learn how to add groups to directory.\n\n\ \Send a search string to find groups or */help* to learn how to add groups to directory.\n\n\
\For example, send _privacy_ to find groups about privacy.\n\n\ \For example, send _privacy_ to find groups about privacy.\n\
\Or send */all* or */new* to list groups.\n\n\
\Content and privacy policy: https://simplex.chat/docs/directory.html" \Content and privacy policy: https://simplex.chat/docs/directory.html"
deGroupInvitation :: Contact -> GroupInfo -> GroupMemberRole -> GroupMemberRole -> IO () deGroupInvitation :: Contact -> GroupInfo -> GroupMemberRole -> GroupMemberRole -> IO ()
@ -201,7 +217,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
"Created the public link to join the group via this directory service that is always online.\n\n\ "Created the public link to join the group via this directory service that is always online.\n\n\
\Please add it to the group welcome message.\n\ \Please add it to the group welcome message.\n\
\For example, add:" \For example, add:"
notifyOwner gr $ "Link to join the group " <> T.unpack displayName <> ": " <> B.unpack (strEncode connReqContact) notifyOwner gr $ "Link to join the group " <> T.unpack displayName <> ": " <> B.unpack (strEncode $ simplexChatContact connReqContact)
CRChatCmdError _ (ChatError e) -> case e of CRChatCmdError _ (ChatError e) -> case e of
CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin." CEGroupUserRole {} -> notifyOwner gr "Failed creating group link, as service is no longer an admin."
CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group." CEGroupMemberUserRemoved -> notifyOwner gr "Failed creating group link, as service is removed from the group."
@ -276,9 +292,10 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
where where
profileUpdate = \case profileUpdate = \case
CRGroupLink {connReqContact} -> CRGroupLink {connReqContact} ->
let groupLink = safeDecodeUtf8 $ strEncode connReqContact let groupLink1 = safeDecodeUtf8 $ strEncode connReqContact
hadLinkBefore = groupLink `isInfix` description p groupLink2 = safeDecodeUtf8 $ strEncode $ simplexChatContact connReqContact
hasLinkNow = groupLink `isInfix` description p' hadLinkBefore = groupLink1 `isInfix` description p || groupLink2 `isInfix` description p
hasLinkNow = groupLink1 `isInfix` description p' || groupLink2 `isInfix` description p'
in if in if
| hadLinkBefore && hasLinkNow -> GPHasServiceLink | hadLinkBefore && hasLinkNow -> GPHasServiceLink
| hadLinkBefore -> GPServiceLinkRemoved | hadLinkBefore -> GPServiceLinkRemoved
@ -379,8 +396,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory."
notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." notifySuperUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)."
deUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand :: ServiceState -> Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO ()
deUserCommand ct ciId = \case deUserCommand env@ServiceState {searchRequests} ct ciId = \case
DCHelp -> DCHelp ->
sendMessage cc ct $ sendMessage cc ct $
"You must be the owner to add the group to the directory:\n\ "You must be the owner to add the group to the directory:\n\
@ -389,20 +406,25 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
\3. You will then need to add this link to the group welcome message.\n\ \3. You will then need to add this link to the group welcome message.\n\
\4. Once the link is added, service admins will approve the group (it can take up to 24 hours), and everybody will be able to find it in directory.\n\n\ \4. Once the link is added, service admins will approve the group (it can take up to 24 hours), and everybody will be able to find it in directory.\n\n\
\Start from inviting the bot to your group as admin - it will guide you through the process" \Start from inviting the bot to your group as admin - it will guide you through the process"
DCSearchGroup s -> DCSearchGroup s -> withFoundListedGroups (Just s) $ sendSearchResults s
getGroups s >>= \case DCSearchNext ->
Just groups -> atomically (TM.lookup (contactId' ct) searchRequests) >>= \case
atomically (filterListedGroups st groups) >>= \case Just search@SearchRequest {searchType, searchTime} -> do
[] -> sendReply "No groups found" currentTime <- getCurrentTime
gs -> do if diffUTCTime currentTime searchTime > 300 -- 5 minutes
sendReply $ "Found " <> show (length gs) <> " group(s)" <> if length gs > 10 then ", sending 10." else "" then do
void . forkIO $ forM_ (take 10 $ sortOn (Down . currentMembers . snd) gs) $ atomically $ TM.delete (contactId' ct) searchRequests
\(GroupInfo {groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) -> do showAllGroups
let membersStr = "_" <> tshow currentMembers <> " members_" else case searchType of
text = groupInfoText p <> "\n" <> membersStr STSearch s -> withFoundListedGroups (Just s) $ sendNextSearchResults takeTop search
msg = maybe (MCText text) (\image -> MCImage {text, image}) image_ STAll -> withFoundListedGroups Nothing $ sendNextSearchResults takeTop search
sendComposedMessage cc ct Nothing msg STRecent -> withFoundListedGroups Nothing $ sendNextSearchResults takeRecent search
Nothing -> sendReply "Error: getGroups. Please notify the developers." Nothing -> showAllGroups
where
showAllGroups = deUserCommand env ct ciId DCAllGroups
DCAllGroups -> withFoundListedGroups Nothing $ sendAllGroups takeTop "top" STAll
DCRecentGroups -> withFoundListedGroups Nothing $ sendAllGroups takeRecent "the most recent" STRecent
DCSubmitGroup _link -> pure ()
DCConfirmDuplicateGroup ugrId gName -> DCConfirmDuplicateGroup ugrId gName ->
atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case atomically (getUserGroupReg st (contactId' ct) ugrId) >>= \case
Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found" Nothing -> sendReply $ "Group ID " <> show ugrId <> " not found"
@ -429,6 +451,54 @@ directoryService st DirectoryOpts {superUsers, serviceName, testing} user@User {
DCCommandError tag -> sendReply $ "Command error: " <> show tag DCCommandError tag -> sendReply $ "Command error: " <> show tag
where where
sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent sendReply = sendComposedMessage cc ct (Just ciId) . textMsgContent
withFoundListedGroups s_ action =
getGroups_ s_ >>= \case
Just groups -> atomically (filterListedGroups st groups) >>= action
Nothing -> sendReply "Error: getGroups. Please notify the developers."
sendSearchResults s = \case
[] -> sendReply "No groups found"
gs -> do
let gs' = takeTop searchResults gs
moreGroups = length gs - length gs'
more = if moreGroups > 0 then ", sending top " <> show (length gs') else ""
sendReply $ "Found " <> show (length gs) <> " group(s)" <> more <> "."
updateSearchRequest (STSearch s) $ groupIds gs'
sendFoundGroups gs' moreGroups
sendAllGroups takeFirst sortName searchType = \case
[] -> sendReply "No groups listed"
gs -> do
let gs' = takeFirst searchResults gs
moreGroups = length gs - length gs'
more = if moreGroups > 0 then ", sending " <> sortName <> " " <> show (length gs') else ""
sendReply $ show (length gs) <> " group(s) listed" <> more <> "."
updateSearchRequest searchType $ groupIds gs'
sendFoundGroups gs' moreGroups
sendNextSearchResults takeFirst SearchRequest {searchType, sentGroups} = \case
[] -> do
sendReply "Sorry, no more groups"
atomically $ TM.delete (contactId' ct) searchRequests
gs -> do
let gs' = takeFirst searchResults $ filterNotSent sentGroups gs
sentGroups' = sentGroups <> groupIds gs'
moreGroups = length gs - S.size sentGroups'
sendReply $ "Sending " <> show (length gs') <> " more group(s)."
updateSearchRequest searchType sentGroups'
sendFoundGroups gs' moreGroups
updateSearchRequest :: SearchType -> Set GroupId -> IO ()
updateSearchRequest searchType sentGroups = do
searchTime <- getCurrentTime
let search = SearchRequest {searchType, searchTime, sentGroups}
atomically $ TM.insert (contactId' ct) search searchRequests
sendFoundGroups gs moreGroups =
void . forkIO $ do
forM_ gs $
\(GroupInfo {groupProfile = p@GroupProfile {image = image_}}, GroupSummary {currentMembers}) -> do
let membersStr = "_" <> tshow currentMembers <> " members_"
text = groupInfoText p <> "\n" <> membersStr
msg = maybe (MCText text) (\image -> MCImage {text, image}) image_
sendComposedMessage cc ct Nothing msg
when (moreGroups > 0) $
sendComposedMessage cc ct Nothing $ MCText $ "Send */next* or just *.* for " <> tshow moreGroups <> " more result(s)."
deSuperUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRSuperUser -> IO () deSuperUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRSuperUser -> IO ()
deSuperUserCommand ct ciId cmd deSuperUserCommand ct ciId cmd

View file

@ -467,6 +467,7 @@ executable simplex-directory-service
other-modules: other-modules:
Directory.Events Directory.Events
Directory.Options Directory.Options
Directory.Search
Directory.Service Directory.Service
Directory.Store Directory.Store
Paths_simplex_chat Paths_simplex_chat
@ -553,6 +554,7 @@ test-suite simplex-chat-test
Broadcast.Options Broadcast.Options
Directory.Events Directory.Events
Directory.Options Directory.Options
Directory.Search
Directory.Service Directory.Service
Directory.Store Directory.Store
Paths_simplex_chat Paths_simplex_chat

View file

@ -30,6 +30,7 @@ directoryServiceTests = do
it "should suspend and resume group" testSuspendResume it "should suspend and resume group" testSuspendResume
it "should join found group via link" testJoinGroup it "should join found group via link" testJoinGroup
it "should support group names with spaces" testGroupNameWithSpaces it "should support group names with spaces" testGroupNameWithSpaces
it "should return more groups in search, all and recent groups" testSearchGroups
describe "de-listing the group" $ do describe "de-listing the group" $ do
it "should de-list if owner leaves the group" testDelistedOwnerLeaves it "should de-list if owner leaves the group" testDelistedOwnerLeaves
it "should de-list if owner is removed from the group" testDelistedOwnerRemoved it "should de-list if owner is removed from the group" testDelistedOwnerRemoved
@ -67,6 +68,7 @@ mkDirectoryOpts tmp superUsers =
superUsers, superUsers,
directoryLog = Just $ tmp </> "directory_service.log", directoryLog = Just $ tmp </> "directory_service.log",
serviceName = "SimpleX-Directory", serviceName = "SimpleX-Directory",
searchResults = 3,
testing = True testing = True
} }
@ -158,7 +160,7 @@ testDirectoryService tmp =
search u s welcome = do search u s welcome = do
u #> ("@SimpleX-Directory " <> s) u #> ("@SimpleX-Directory " <> s)
u <# ("SimpleX-Directory> > " <> s) u <# ("SimpleX-Directory> > " <> s)
u <## " Found 1 group(s)" u <## " Found 1 group(s)."
u <# "SimpleX-Directory> PSA (Privacy, Security & Anonymity)" u <# "SimpleX-Directory> PSA (Privacy, Security & Anonymity)"
u <## "Welcome message:" u <## "Welcome message:"
u <## welcome u <## welcome
@ -206,7 +208,7 @@ testJoinGroup tmp =
cath `connectVia` dsLink cath `connectVia` dsLink
cath #> "@SimpleX-Directory privacy" cath #> "@SimpleX-Directory privacy"
cath <# "SimpleX-Directory> > privacy" cath <# "SimpleX-Directory> > privacy"
cath <## " Found 1 group(s)" cath <## " Found 1 group(s)."
cath <# "SimpleX-Directory> privacy (Privacy)" cath <# "SimpleX-Directory> privacy (Privacy)"
cath <## "Welcome message:" cath <## "Welcome message:"
welcomeMsg <- getTermLine cath welcomeMsg <- getTermLine cath
@ -263,6 +265,92 @@ testGroupNameWithSpaces tmp =
bob <# "SimpleX-Directory> The group ID 1 (Privacy & Security) is listed in the directory again!" bob <# "SimpleX-Directory> The group ID 1 (Privacy & Security) is listed in the directory again!"
groupFound bob "Privacy & Security" groupFound bob "Privacy & Security"
testSearchGroups :: HasCallStack => FilePath -> IO ()
testSearchGroups tmp =
withDirectoryService tmp $ \superUser dsLink ->
withNewTestChat tmp "bob" bobProfile $ \bob -> do
withNewTestChat tmp "cath" cathProfile $ \cath -> do
bob `connectVia` dsLink
cath `connectVia` dsLink
forM_ [1..8 :: Int] $ \i -> registerGroupId superUser bob (groups !! (i - 1)) "" i i
connectUsers bob cath
fullAddMember "MyGroup" "" bob cath GRMember
joinGroup "MyGroup" cath bob
cath <## "#MyGroup: member SimpleX-Directory_1 is connected"
cath <## "contact and member are merged: SimpleX-Directory, #MyGroup SimpleX-Directory_1"
cath <## "use @SimpleX-Directory <message> to send messages"
cath #> "@SimpleX-Directory MyGroup"
cath <# "SimpleX-Directory> > MyGroup"
cath <## " Found 7 group(s), sending top 3."
receivedGroup cath 0 3
receivedGroup cath 1 2
receivedGroup cath 2 2
cath <# "SimpleX-Directory> Send /next or just . for 4 more result(s)."
cath #> "@SimpleX-Directory /next"
cath <# "SimpleX-Directory> > /next"
cath <## " Sending 3 more group(s)."
receivedGroup cath 3 2
receivedGroup cath 4 2
receivedGroup cath 5 2
cath <# "SimpleX-Directory> Send /next or just . for 1 more result(s)."
-- search of another user does not affect the search of the first user
groupFound bob "Another"
cath #> "@SimpleX-Directory ."
cath <# "SimpleX-Directory> > ."
cath <## " Sending 1 more group(s)."
receivedGroup cath 6 2
cath #> "@SimpleX-Directory /all"
cath <# "SimpleX-Directory> > /all"
cath <## " 8 group(s) listed, sending top 3."
receivedGroup cath 0 3
receivedGroup cath 1 2
receivedGroup cath 2 2
cath <# "SimpleX-Directory> Send /next or just . for 5 more result(s)."
cath #> "@SimpleX-Directory /new"
cath <# "SimpleX-Directory> > /new"
cath <## " 8 group(s) listed, sending the most recent 3."
receivedGroup cath 7 2
receivedGroup cath 6 2
receivedGroup cath 5 2
cath <# "SimpleX-Directory> Send /next or just . for 5 more result(s)."
cath #> "@SimpleX-Directory term3"
cath <# "SimpleX-Directory> > term3"
cath <## " Found 3 group(s)."
receivedGroup cath 4 2
receivedGroup cath 5 2
receivedGroup cath 6 2
cath #> "@SimpleX-Directory term1"
cath <# "SimpleX-Directory> > term1"
cath <## " Found 6 group(s), sending top 3."
receivedGroup cath 1 2
receivedGroup cath 2 2
receivedGroup cath 3 2
cath <# "SimpleX-Directory> Send /next or just . for 3 more result(s)."
cath #> "@SimpleX-Directory ."
cath <# "SimpleX-Directory> > ."
cath <## " Sending 3 more group(s)."
receivedGroup cath 4 2
receivedGroup cath 5 2
receivedGroup cath 6 2
where
groups :: [String]
groups =
[ "MyGroup",
"MyGroup term1 1",
"MyGroup term1 2",
"MyGroup term1 term2",
"MyGroup term1 term2 term3",
"MyGroup term1 term2 term3 term4",
"MyGroup term1 term2 term3 term4 term5",
"Another"
]
receivedGroup :: TestCC -> Int -> Int -> IO ()
receivedGroup u ix count = do
u <#. ("SimpleX-Directory> " <> groups !! ix)
u <## "Welcome message:"
u <##. "Link to join the group "
u <## (show count <> " members")
testDelistedOwnerLeaves :: HasCallStack => FilePath -> IO () testDelistedOwnerLeaves :: HasCallStack => FilePath -> IO ()
testDelistedOwnerLeaves tmp = testDelistedOwnerLeaves tmp =
withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink -> withDirectoryServiceCfg tmp testCfgCreateGroupDirect $ \superUser dsLink ->
@ -930,6 +1018,7 @@ u `connectVia` dsLink = do
u <## "Send a search string to find groups or /help to learn how to add groups to directory." u <## "Send a search string to find groups or /help to learn how to add groups to directory."
u <## "" u <## ""
u <## "For example, send privacy to find groups about privacy." u <## "For example, send privacy to find groups about privacy."
u <## "Or send /all or /new to list groups."
u <## "" u <## ""
u <## "Content and privacy policy: https://simplex.chat/docs/directory.html" u <## "Content and privacy policy: https://simplex.chat/docs/directory.html"
@ -967,7 +1056,7 @@ groupFoundN :: Int -> TestCC -> String -> IO ()
groupFoundN count u name = do groupFoundN count u name = do
u #> ("@SimpleX-Directory " <> name) u #> ("@SimpleX-Directory " <> name)
u <# ("SimpleX-Directory> > " <> name) u <# ("SimpleX-Directory> > " <> name)
u <## " Found 1 group(s)" u <## " Found 1 group(s)."
u <#. ("SimpleX-Directory> " <> name) u <#. ("SimpleX-Directory> " <> name)
u <## "Welcome message:" u <## "Welcome message:"
u <##. "Link to join the group " u <##. "Link to join the group "