{-# LANGUAGE OverloadedStrings #-}
{-|
Module: SignalDBus
Description: Bindings for the DBus interface of signal-cli
Copyright: (c) Lia Lenckowski, 2022
License: AGPL
Maintainer: lialenck@protonmail.com
Stability: stable
Portability: GNU/Linux, MacOS
This module is the main entry point for this library.
To get started, install and set up [signal-cli](https://github.com/asamk/signal-cli),
and make sure it's running in daemon mode, with the dbus interface enabled. Then, as
an example, you could use the following code to react with "π" to every message
received from a specific user either via PM or in groups you're a member of:
> import Control.Monad (forever, when)
> import SignalDBus
>
> reactWithSkull :: Timestamp -> String -> Maybe Group -> SignalConn -> IO Timestamp
> reactWithSkull ts n mayG sc =
> case mayG of
> Nothing -> sendMessageReaction "π" False n ts n sc
> Just g -> sendGroupMessageReaction "π" False n ts g sc
>
> -- react with π to every message received by a specific user
> main :: IO ()
> main = do
> let num = "[insert phone number with country code]"
> withConn $ \sc -> do
> n <- getSelfNumber sc
> putStrLn $ "Running on: " ++ show n
> withReceiveMessages sc $ \getMsg -> forever $ do
> getMsg >>= \case
> Receipt _ _ -> return ()
> SyncMessage _ _ _ _ _ -> return ()
> Message ts n g _ _ -> when (n == num) $ do
> reactWithSkull ts num g sc
> return ()
>
> return ()
All of the functions exported may fail with a 'DBus.Client.ClientError' if something
goes wrong. None of the functions should throw any other exception.
Quite a few of these functions have a scary-looking \"UNTESTED\" in their documentation.
If you end up using any of those, and they work, please tell me so I can remove that
disclaimer.
This package aims to provide somewhat direct bindings for all methods described by
<https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc>. In cases
where methods are overloaded, this library almost always chooses to implement the more
general ones. Some functionality of the dbus interface of signal-cli isn't implemented,
as it's either not documented or not often useful; if you require any of that, I'd
recommend looking at SignalDBus.Interface.
-}
module SignalDBus (
-- * Opening connections
SignalDBus.Interface.withConn,
SignalDBus.Interface.withConnNum,
-- * Receiving Messages
SignalDBus.Interface.withReceiveMessages,
-- * Utility functions
reactTo,
replyTo,
-- * Control interface
-- $control
link,
listAccounts,
register,
registerWithCaptcha,
verify,
verifyWithPin,
-- * Account interface
-- $signal
getContactName,
getContactNumber,
getSelfNumber,
isContactBlocked,
isRegistered,
listNumbers,
removePin,
sendEndSessionMessage,
sendMessage,
sendMessageReaction,
sendNoteToSelfMessage,
sendReadReceipt,
sendViewedReceipt,
sendRemoteDeleteMessage,
sendTyping,
setContactBlocked,
setContactName,
deleteContact,
deleteRecipient,
setExpirationTimer,
setPin,
submitRateLimitChallenge,
updateProfile,
uploadStickerPack,
version,
createGroup,
getGroup,
joinGroup,
listGroups,
sendGroupMessage,
sendGroupTyping,
sendGroupMessageReaction,
sendGroupRemoteDeleteMessage,
addDevice,
getDevice,
listDevices,
-- * Group interface
getGroupId,
getGroupName,
getGroupDescription,
getGroupIsBlocked,
getGroupIsAdmin,
getGroupMessageExpirationTimer,
getGroupMembers,
getGroupPendingMembers,
getGroupRequestingMembers,
getGroupAdmins,
getGroupPermissionAddMember,
getGroupPermissionEditDetails,
getGroupPermissionSendMessage,
-- could've been 'getGroupGroupInviteLink' for consistency, but eww.
getGroupInviteLink,
setGroupName,
setGroupDescription,
setGroupAvatar,
setGroupIsBlocked,
setGroupMessageExpirationTimer,
setGroupPermissionAddMember,
setGroupPermissionEditDetails,
setGroupPermissionSendMessage,
groupAddAdmins,
groupAddMembers,
groupDisableLink,
groupEnableLink,
groupQuit,
groupRemoveAdmins,
groupRemoveMembers,
groupResetLink,
-- TODO: * Device interface
-- * Types
SignalDBus.Types.SignalConn,
SignalDBus.Types.Timestamp,
SignalDBus.Types.Device,
SignalDBus.Types.Group,
SignalDBus.Types.ReceivedMessage(..),
fromUTCTime,
toUTCTime,
) where
import Data.ByteString (ByteString)
import Data.Int (Int32, Int64)
import DBus.Internal.Types (toVariant, ObjectPath(..))
import DBus.Client (clientError)
import SignalDBus.Interface
import SignalDBus.Types
import UnliftIO (MonadIO, throwIO)
-- |Utility function for sending a message in the same chat as you received a message from
replyTo :: MonadIO m
=> ReceivedMessage -- ^ Message in whoose chat the reply should be sent
-> String -- ^ Message content
-> [String] -- ^ Paths to attachments. Resolves using the working dir of the signal-cli daemon, not this process
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of sent message
replyTo (Message _ _ (Just gId) _ _) msg as sc = sendGroupMessage msg as gId sc
replyTo (Message _ n Nothing _ _) msg as sc = sendMessage msg as [n] sc
replyTo (SyncMessage _ _ _ _ _) _ _ _ = throwIO $ clientError $ "replyTo called on SyncMessage"
replyTo (Receipt _ _) _ _ _ = throwIO $ clientError $ "replyTo called on SyncMessage"
-- |Utility function for reacting to a received message
reactTo :: MonadIO m
=> ReceivedMessage -- ^ Message which to react to
-> String -- ^ Unicode grapheme cluster. Only tested with "π"
-> Bool -- ^ whether to remove an existing reaction
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of sent message
reactTo (Message ts n (Just gId) _ _) emoji rm sc = sendGroupMessageReaction emoji rm n ts gId sc
reactTo (Message ts n Nothing _ _) emoji rm sc = sendMessageReaction emoji rm n ts n sc
reactTo (SyncMessage _ _ _ _ _) _ _ _ = throwIO $ clientError $ "replyTo called on SyncMessage"
reactTo (Receipt _ _) _ _ _ = throwIO $ clientError $ "replyTo called on SyncMessage"
-- $control
--
-- These methods are only available if the signal-cli daemon was started in
-- multi-account mode.
-- |UNTESTED. Link this as a new device, identified by the given string.
link :: MonadIO m => String -> SignalConn -> m String
link name = callControl "link" [toVariant name]
-- |List accounts attached to this signal-cli instance
listAccounts :: MonadIO m => SignalConn -> m [String]
listAccounts sc = do
obs <- callControl "listAccounts" [] sc
return $! flip map obs $ \(ObjectPath s)
-> '+' : drop (length ("/org/asamk/Signal/_" :: String)) s
-- |UNTESTED. Register this as the primary device for the given number. Set the
-- second argument to 'True' to request voice verification instead of SMS verification.
register :: MonadIO m => String -> Bool -> SignalConn -> m ()
register name b = callControl_ "register" [toVariant name, toVariant b]
-- |UNTESTED. Same as 'register', but include a Captcha string.
registerWithCaptcha :: MonadIO m => String -> Bool -> String -> SignalConn -> m ()
registerWithCaptcha name b c = callControl_ "registerWithCaptcha"
$ [toVariant name, toVariant b, toVariant c]
-- | UNTESTED. Verify your phone number after requesting registration via 'register'
-- or 'registerWithCaptcha'
verify :: MonadIO m
=> String -- ^ Phone number
-> String -- ^ Verification code
-> SignalConn -- ^ Connection object
-> m ()
verify n c = callControl_ "verify" [toVariant n, toVariant c]
-- | UNTESTED. Same as 'verify', but include a registration pin for protected accounts
verifyWithPin :: MonadIO m
=> String -- ^ Phone number
-> String -- ^ Verification code
-> String -- ^ Pin
-> SignalConn -- ^ Connection object
-> m ()
verifyWithPin n c p = callControl_ "verifyWithPin"
$ [toVariant n, toVariant c, toVariant p]
-- $signal
--
-- These methods may only be called on a connection object that knows which account to
-- use; that is, either the signal daemon has a default account, and you've used
-- 'withConn', or it doesn't, and you've used 'withConnNum'.
-- |Takes a number, and returns the name of the contact, empty if unknown
getContactName :: MonadIO m => String -> SignalConn -> m String
getContactName n = callSC "getContactName" [toVariant n]
-- |Takes a contact name, and returns known numbers, both as strings
getContactNumber :: MonadIO m => String -> SignalConn -> m [String]
getContactNumber name = callSC "getContactNumber" [toVariant name]
-- |Returns your own number
getSelfNumber :: MonadIO m => SignalConn -> m String
getSelfNumber = callSC "getSelfNumber" []
-- |Returns true if you blocked this number
isContactBlocked :: MonadIO m => String -> SignalConn -> m Bool
isContactBlocked number = callSC "isContactBlocked" [toVariant number]
-- |For each given number, returns whether that user is registered on Signal
isRegistered :: MonadIO m => [String] -> SignalConn -> m [Bool]
isRegistered nums = callSC "isRegistered" [toVariant nums]
-- |List all known numbers (e.g. group members and senders of received messages)
listNumbers :: MonadIO m => SignalConn -> m [String]
listNumbers = callSC "listNumbers" []
-- |UNTESTED. Removes registration pin protection.
removePin :: MonadIO m => SignalConn -> m ()
removePin = callSC_ "removePin" []
-- |UNTESTED. No idea what this accomplishes, so hmu if you know. It's implemented though
sendEndSessionMessage :: MonadIO m => SignalConn -> m ()
sendEndSessionMessage = callSC_ "sendEndSessionMessage" []
-- |Sends a message, possibly with attachments, to a number of recipients
sendMessage :: MonadIO m
=> String -- ^ Message content
-> [String] -- ^ Paths to attachments. Resolves using the working dir of the signal-cli daemon, not this process
-> [String] -- ^ Recipients
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of sent message
sendMessage m as rs = callSC "sendMessage"
[toVariant m, toVariant as, toVariant rs]
-- |Reacts to a message
sendMessageReaction :: MonadIO m
=> String -- ^ Unicode grapheme cluster. Only tested with "π"
-> Bool -- ^ Whether to remove a previously sent reaction instead of adding one
-> String -- ^ Author of the message you want to react to
-> Timestamp -- ^ Timestamp of message to react to
-> String -- ^ Phone numbers of recipient
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of reaction
sendMessageReaction emoji rm n ts r = callSC "sendMessageReaction"
$ [toVariant emoji, toVariant rm, toVariant n, toVariant ts, toVariant r]
-- |Sends a message to yourself
sendNoteToSelfMessage :: MonadIO m
=> String -- ^ Message
-> [String] -- ^ Paths to attachments. Resolves using the working dir of the signal-cli daemon, not this process
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of sent message
sendNoteToSelfMessage m as = callSC "sendNoteToSelfMessage"
$ [toVariant m, toVariant as]
-- |Sends read receipts for the messages with the specified timestamps to the given phone
-- number. These timestamps must belong to messages you received from that number.
sendReadReceipt :: MonadIO m => String -> [Timestamp] -> SignalConn -> m ()
sendReadReceipt r ts = callSC_ "sendReadReceipt" [toVariant r, toVariant ts]
-- |Sends viewed receipts for the messages with the specified timestamps to the given phone
-- number. This is probably not what you want; 'sendReadReceipt' seems to be the way to go.
sendViewedReceipt :: MonadIO m => String -> [Timestamp] -> SignalConn -> m ()
sendViewedReceipt r ts = callSC_ "sendViewedReceipt" [toVariant r, toVariant ts]
-- |Delete one of you own private messages; also deletes them remotely on supported
-- clients (Signal-desktop, Android/IOS apps, ...)
sendRemoteDeleteMessage :: MonadIO m
=> Timestamp -- ^ Timestamp of message to delete
-> [String] -- ^ Recipients
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp, can be used to identify corresponding replies
sendRemoteDeleteMessage ts rs = callSC "sendRemoteDeleteMessage"
$ [toVariant ts, toVariant rs]
-- |Send updates about whether you're typing to the given number.
-- NOTE: the boolean argument should be False to indicate you're typing, and True to
-- clear the typing state
sendTyping :: MonadIO m => String -> Bool -> SignalConn -> m ()
sendTyping n b = callSC_ "sendTyping" [toVariant n, toVariant b]
-- |UNTESTED. Sets whether a contact is blocked. This is only done locally, so this just
-- disabled messages from that number from being forwarded via DBus.
setContactBlocked :: MonadIO m => String -> Bool -> SignalConn -> m ()
setContactBlocked n b = callSC_ "setContactBlocked" [toVariant n, toVariant b]
-- |Not implemented, as the documentation doesn't state the type of the name
-- (probably a String, but I dont feel like testing stuff rn)
setContactName :: a
setContactName = error "Not implemented due to documentation succ"
-- |UNTESTED. Probably deletes a contact, given by a phone number, or something.
deleteContact :: MonadIO m => String -> SignalConn -> m ()
deleteContact n = callSC_ "deleteContact" [toVariant n]
-- |UNTESTED. Idk what this does, but tell me if you know/have used this.
deleteRecipient :: MonadIO m => String -> SignalConn -> m ()
deleteRecipient n = callSC_ "deleteRecipient" [toVariant n]
-- |Set seconds until messages to this recipient dissapear (on supported clients). Set to
-- 0 to disable.
setExpirationTimer :: (MonadIO m, Integral i) => String -> i -> SignalConn -> m ()
setExpirationTimer n i = callSC_ "setExpirationTimer"
$ [toVariant n, toVariant (fromIntegral i :: Int32)]
-- |UNTESTED. Set registration pin to prevent others from registering your number.
setPin :: MonadIO m => String -> SignalConn -> m ()
setPin p = callSC_ "setPin" [toVariant p]
-- |UNTESTED. Idk but seems to be useful for lifting rate limits
submitRateLimitChallenge :: MonadIO m => String -> String -> SignalConn -> m ()
submitRateLimitChallenge ch cap = callSC_ "submitRateLimitChallenge"
$ [toVariant ch, toVariant cap]
-- |Update parts of your profile. You can leave any string field empty to keep the old
-- value.
updateProfile :: MonadIO m
=> String -- ^ Given name
-> String -- ^ Family name
-> String -- ^ About message
-> String -- ^ Emoji for profile
-> String -- ^ Avatar file name. Resolves using the working dir of the signal-cli daemon, not this process
-> Bool -- ^ True if the avatar should be removed
-> SignalConn -- ^ Connection object
-> m ()
updateProfile gn fn ab em fp rm = callSC_ "updateProfile"
$ [toVariant gn, toVariant fn, toVariant ab, toVariant em, toVariant fp, toVariant rm]
-- |UNTESTED. Uploads a sticker pack, given by the path to a manifest.json or zip file,
-- and return the URL of the pack.
uploadStickerPack :: MonadIO m => String -> SignalConn -> m String
uploadStickerPack fp = callSC "uploadStickerPack" [toVariant fp]
-- |Return version string of signal-cli. This also works for multi-account connections.
version :: MonadIO m => SignalConn -> m String
version = callSC "version" []
-- |Create a group
createGroup :: MonadIO m
=> String -- ^ Group name
-> [String] -- ^ Initial members
-> String -- ^ Avatar file name. Resolves using the working dir of the signal-cli daemon, not this process
-> SignalConn -- ^ Connection object
-> m Group -- ^ Group object
createGroup n ms f sc = do
gId <- callSC "createGroup" [toVariant n, toVariant ms, toVariant f] sc
getGroup gId sc
-- |Get DBus object path for a group, given by its signal-internal binary identifier.
getGroup :: MonadIO m => ByteString -> SignalConn -> m Group
getGroup g = callSC "getGroup" [toVariant g]
-- |UNTESTED. Join a group given by an invite link. Behaviour of this depends on
-- properties of the group; see "joinGroup" on
-- https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc
-- (latest commit while writing this: 34cc64f8ce97a63c859bd95faf6783422f14df61)
joinGroup :: MonadIO m => String -> SignalConn -> m ()
joinGroup l = callSC_ "joinGroup" [toVariant l]
-- |List known groups, represented as (group object, internal identifier, group name)
listGroups :: MonadIO m => SignalConn -> m [(Group, ByteString, String)]
listGroups = callSC "listGroups" []
-- |Sends a message, possibly with attachments, to a group
sendGroupMessage :: MonadIO m
=> String -- ^ Message content
-> [String] -- ^ Paths to attachments. Resolves using the working dir of the signal-cli daemon, not this process
-> Group -- ^ Group in which to send a message
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of sent message
sendGroupMessage m as g sc = do
gId <- getGroupProp "Id" g sc
callSC "sendGroupMessage"
[toVariant m, toVariant as, toVariant (gId :: ByteString)] sc
-- |Send updates about whether you're typing to the given group.
-- NOTE: the boolean argument should be False to indicate you're typing, and True to
-- clear the typing state
sendGroupTyping :: MonadIO m => Group -> Bool -> SignalConn -> m ()
sendGroupTyping g b sc = do
gId <- getGroupId g sc
callSC_ "sendGroupTyping" [toVariant gId, toVariant b] sc
-- |Reacts to a message
sendGroupMessageReaction :: MonadIO m
=> String -- ^ Unicode grapheme cluster. Only tested with "π"
-> Bool -- ^ Whether to remove a previously sent reaction instead of adding one
-> String -- ^ Author of the message you want to react to
-> Timestamp -- ^ Timestamp of message to react to
-> Group -- ^ Group in which to react
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp of reaction
sendGroupMessageReaction emoji rm n ts g sc = do
gId <- getGroupId g sc
callSC "sendGroupMessageReaction" [toVariant emoji, toVariant rm, toVariant n,
toVariant ts, toVariant gId] sc
-- |Delete one of you own group messages; also deletes them remotely on supported
-- clients (Signal-desktop, Android/IOS apps, ...)
sendGroupRemoteDeleteMessage :: MonadIO m
=> Timestamp -- ^ Timestamp of message to delete
-> Group -- ^ Group in which to delete your message
-> SignalConn -- ^ Connection object
-> m Timestamp -- ^ Timestamp, can be used to identify corresponding replies
sendGroupRemoteDeleteMessage ts g sc = do
gId <- getGroupId g sc
callSC "sendGroupRemoteDeleteMessage" [toVariant ts, toVariant gId] sc
-- |UNTESTED. Add a device that wants to link to this account using a link
addDevice :: MonadIO m => String -> SignalConn -> m ()
addDevice l = callSC_ "addDevice" [toVariant l]
-- |Get DBus object path for a device given by a device ID
getDevice :: MonadIO m => Int64 -> SignalConn -> m ObjectPath
getDevice d = callSC "getDevice" [toVariant d]
-- |List devices linked with this account, represented as
-- (object path, device id, device name)
listDevices :: MonadIO m => SignalConn -> m [(ObjectPath, Int64, String)]
listDevices = callSC "listDevices" []
-- |Byte array representing the internal group identifier
getGroupId :: MonadIO m => Group -> SignalConn -> m ByteString
getGroupId = getGroupProp "Id"
-- |Display name
getGroupName :: MonadIO m => Group -> SignalConn -> m String
getGroupName = getGroupProp "Name"
-- |Description
getGroupDescription :: MonadIO m => Group -> SignalConn -> m String
getGroupDescription = getGroupProp "Description"
-- |UNTESTED. If true, messages won't be forwarded via DBus
getGroupIsBlocked :: MonadIO m => Group -> SignalConn -> m Bool
getGroupIsBlocked = getGroupProp "IsBlocked"
-- |Whether this account is a group admin
getGroupIsAdmin :: MonadIO m => Group -> SignalConn -> m Bool
getGroupIsAdmin = getGroupProp "IsAdmin"
-- |Message expiration timer. 0 if disabled
getGroupMessageExpirationTimer :: MonadIO m => Group -> SignalConn -> m Int
getGroupMessageExpirationTimer g sc = do
i <- getGroupProp "MessageExpirationTimer" g sc
return $! fromIntegral (i :: Int32)
-- |List of group members' phone numbers
getGroupMembers :: MonadIO m => Group -> SignalConn -> m [String]
getGroupMembers = getGroupProp "Members"
-- |UNTESTED. List of pending group members; I don't know what this does, but I
-- imagine you're probably looking for 'getGroupRequestingMembers'
getGroupPendingMembers :: MonadIO m => Group -> SignalConn -> m [String]
getGroupPendingMembers = getGroupProp "PendingMembers"
-- |List of phone numbers requesting to join the group, if theres an invite link set
-- to require admin approval
getGroupRequestingMembers :: MonadIO m => Group -> SignalConn -> m [String]
getGroupRequestingMembers = getGroupProp "RequestingMembers"
-- |List of group admins' phone numbers
getGroupAdmins :: MonadIO m => Group -> SignalConn -> m [String]
getGroupAdmins = getGroupProp "Admins"
-- |String representing who has permission to add members
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
getGroupPermissionAddMember :: MonadIO m => Group -> SignalConn -> m String
getGroupPermissionAddMember = getGroupProp "PermissionAddMember"
-- |String representing who has permission to edit group description
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
getGroupPermissionEditDetails :: MonadIO m => Group -> SignalConn -> m String
getGroupPermissionEditDetails = getGroupProp "PermissionEditDetails"
-- |String representing who has permission to send messages
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
getGroupPermissionSendMessage :: MonadIO m => Group -> SignalConn -> m String
getGroupPermissionSendMessage = getGroupProp "PermissionSendMessage"
-- |Group invitation link. Empty if disabled
getGroupInviteLink :: MonadIO m => Group -> SignalConn -> m String
getGroupInviteLink = getGroupProp "GroupInviteLink"
-- |Display name
setGroupName :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupName s = setGroupProp "Name" s
-- |Description
setGroupDescription :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupDescription s = setGroupProp "Description" s
-- |Filename of group avatar. Resolves using the working dir of the signal-cli daemon, not this process
setGroupAvatar :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupAvatar s = setGroupProp "Avatar" s
-- |UNTESTED. If true, messages won't be forwarded via DBus
setGroupIsBlocked :: MonadIO m => Bool -> Group -> SignalConn -> m ()
setGroupIsBlocked b = setGroupProp "IsBlocked" b
-- |Message expiration timer. 0 to disable
setGroupMessageExpirationTimer :: MonadIO m => Int -> Group -> SignalConn -> m ()
setGroupMessageExpirationTimer i = setGroupProp "MessageExpirationTimer"
$ (fromIntegral i :: Int32)
-- |String representing who has permission to add members
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
setGroupPermissionAddMember :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupPermissionAddMember s = setGroupProp "PermissionAddMember" s
-- |String representing who has permission to edit group description
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
setGroupPermissionEditDetails :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupPermissionEditDetails s = setGroupProp "PermissionEditDetails" s
-- |String representing who has permission to send messages
-- (one of "ONLY_ADMINS", "EVERY_MEMBER")
setGroupPermissionSendMessage :: MonadIO m => String -> Group -> SignalConn -> m ()
setGroupPermissionSendMessage s = setGroupProp "PermissionSendMessage" s
-- |Add admins
groupAddAdmins :: MonadIO m => [String] -> Group -> SignalConn -> m ()
groupAddAdmins ns = callGroup_ "addAdmins" [toVariant ns]
-- |Add numbers who are pending members to the group, and other numbers to requesting
-- members list
groupAddMembers :: MonadIO m => [String] -> Group -> SignalConn -> m ()
groupAddMembers ns = callGroup_ "addMembers" [toVariant ns]
-- |Disable group invitation link
groupDisableLink :: MonadIO m => Group -> SignalConn -> m ()
groupDisableLink = callGroup_ "disableLink" []
-- |Enable group invitation link. If the argument is true, users cannot join directly,
-- but are added to the requesting members list first.
groupEnableLink :: MonadIO m => Bool -> Group -> SignalConn -> m ()
groupEnableLink b = callGroup_ "enableLink" [toVariant b]
-- |Quit the group
groupQuit :: MonadIO m => Group -> SignalConn -> m ()
groupQuit = callGroup_ "quitGroup" []
-- |Remove admins
groupRemoveAdmins :: MonadIO m => [String] -> Group -> SignalConn -> m ()
groupRemoveAdmins ns = callGroup_ "removeAdmins" [toVariant ns]
-- |Remove numbers from the group
groupRemoveMembers :: MonadIO m => [String] -> Group -> SignalConn -> m ()
groupRemoveMembers ns = callGroup_ "removeMembers" [toVariant ns]
-- |Reset the group invitation link
groupResetLink :: MonadIO m => Group -> SignalConn -> m ()
groupResetLink = callGroup_ "resetLink" []