Calamity

Hackage Gitlab pipeline status License Hackage-Deps

Calamity is a Haskell library for writing discord bots, it uses [Polysemy](https://hackage.haskell.org/package/polysemy) as the core library for handling effects, allowing you to pick and choose how to handle certain features of the library. The current customisable effects are: * Cache: The default cache handler keeps the cache in memory, however you could write a cache handler that stores cache in a database for example. * Metrics: The library has counters, gauges, and histograms installed to measure useful things, by default these are not used (and cost nothing), but could be combined with [Prometheus](https://hackage.haskell.org/package/prometheus). An example of using prometheus as the metrics handler can be found [here](https://github.com/nitros12/calamity-example). # Docs You can find documentation on hackage: [here](https://hackage.haskell.org/package/calamity) # Example An example project can be found at: [nitros12/calamity-example](https://github.com/nitros12/calamity-example) ``` haskell {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} module Main where import Calamity import Calamity.Cache.InMemory import Calamity.Metrics.Noop import Control.Concurrent import Control.Concurrent.STM.TVar import Control.Lens import Control.Monad import Data.Text.Lazy ( Text, fromStrict ) import Data.Text.Strict.Lens import qualified DiPolysemy as DiP import qualified Polysemy as P import qualified Polysemy.Async as P import qualified Polysemy.AtomicState as P import qualified Polysemy.Embed as P import qualified Polysemy.Fail as P import Prelude hiding ( error ) import TextShow data Counter m a where GetCounter :: Counter m Int P.makeSem ''Counter runCounterAtomic :: P.Member (P.Embed IO) r => P.Sem (Counter ': r) a -> P.Sem r a runCounterAtomic m = do var <- P.embed $ newTVarIO (0 :: Int) P.runAtomicStateTVar var $ P.reinterpret (\case GetCounter -> P.atomicState (\v -> (v + 1, v))) m handleFailByLogging m = do r <- P.runFail m case r of Left e -> DiP.error (e ^. packed) _ -> pure () info = DiP.info @Text debug = DiP.info @Text tellt :: (BotC r, Tellable t) => t -> Text -> P.Sem r (Either RestError Message) tellt = tell @Text main :: IO () main = do token <- view packed <$> getEnv "BOT_TOKEN" void . P.runFinal . P.embedToFinal . runCounterAtomic . runCacheInMemory . runMetricsPrometheusIO $ runBotIO (BotToken token) $ do react @'MessageCreateEvt $ \msg -> handleFailByLogging $ case msg ^. #content of "!count" -> replicateM_ 3 $ do val <- getCounter info $ "the counter is: " <> showt val void $ tellt msg ("The value is: " <> showt val) "!say hi" -> replicateM_ 3 . P.async $ do info "saying heya" Right msg' <- tellt msg "heya" info "sleeping" P.embed $ threadDelay (5 * 1000 * 1000) info "slept" void . invoke $ EditMessage (msg ^. #channelID) msg' (Just "lol") Nothing info "edited" "!explode" -> do Just x <- pure Nothing debug "unreachable!" "!bye" -> do void $ tellt msg "bye!" stopBot "!fire-evt" -> fire $ customEvt @"my-event" ("aha" :: Text, msg) "!wait" -> do void $ tellt msg "waiting for !continue" waitUntil @'MessageCreateEvt (\msg -> (debug $ "got message: " <> showt msg) >> (pure $ msg ^. #content == "!continue")) void $ tellt msg "got !continue" _ -> pure () react @('CustomEvt "my-event" (Text, Message)) $ \(s, m) -> void $ tellt m ("Somebody told me to tell you about: " <> s) ```