discord-haskell icon indicating copy to clipboard operation
discord-haskell copied to clipboard

Question: How should I do to use a state variable in bot loop?

Open Tatsuki-I opened this issue 5 years ago • 4 comments

Thank you for your making about this library.

I have a question. I'm creating a game dealer bot which can deal some games on discord bot. And, I chose this library. But, I can't understand how to use some state variables.

For example, I made a command join . Users who want to join the game should send this command, and bot updates players :: [Users] variable.

How should I do?

Tatsuki-I avatar Jun 03 '20 13:06 Tatsuki-I

Good question, it's not very intuitive how to get state into this library. Here's an example I drafted that I should add to the existing examples.

We need to use a reference type (MVar a, IORef a, or something), and use IO functions to read and write to the shared variable. Pass this reference into discordOnEvent to have access to it.

{-# LANGUAGE OverloadedStrings #-}  -- allows "strings" to be Data.Text

import Control.Monad (when, forM_)
import Control.Concurrent (threadDelay)
import Data.Char (toLower)
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Data.IORef

import Discord
import Discord.Types
import qualified Discord.Requests as R

data State = State { users :: S.Set UserId }

stateExample :: IO ()
stateExample = do
  state <- newIORef (State (S.empty))  -- create state
  tok <- TIO.readFile "./examples/auth-token.secret"

  t <- runDiscord $ def { discordToken = tok
                        , discordOnEnd = putStrLn "Ended"
                        , discordOnEvent = eventHandler state -- pass state here!

                        , discordForkThreadForEvents = False
                        -- Optional, but lets us use IORef instead of a
                        -- concurrency handler like MVar.
                        }
  threadDelay (1 `div` 10 * 10^6)
  TIO.putStrLn t

-- handler now knows state
eventHandler :: IORef State -> DiscordHandle -> Event -> IO ()
eventHandler state dis event = case event of
      MessageCreate m -> when (not (fromBot m) && isJoin m) $ do
        _ <- restCall dis (R.CreateMessage (messageChannel m) "Joining!")
        -- add user to state
        modifyIORef state (addUser m)

        -- print it or something
        s <- readIORef state
        print $ S.toList (users s)
        pure ()
      _ -> pure ()

-- in a real program, this function has ALL of the state logic, and will
--  be VERY large and complicated. Here we just add the userid to the 
--  existing state
addUser :: Message -> State -> State
addUser m s = State {
  users = S.union (users s)
                  (S.fromList [userId (messageAuthor m)] )
  }

fromBot :: Message -> Bool
fromBot m = userIsBot (messageAuthor m)

isJoin :: Message -> Bool
isJoin = ("join" `T.isPrefixOf`) . T.map toLower . messageText

aquarial avatar Jun 03 '20 15:06 aquarial

Thank you! I've done what I want to do. I think it should be written into documents.

Tatsuki-I avatar Jun 16 '20 05:06 Tatsuki-I

Added an example https://github.com/aquarial/discord-haskell/blob/master/examples/state-counter.hs

Documentation is a good idea, need to figure out what kind. Github wiki or markdown or other.

aquarial avatar Dec 19 '21 00:12 aquarial

Just wanted to say that this thread and example was very helpful, thank you!

ghost avatar Oct 17 '22 20:10 ghost