GithubHelp home page GithubHelp logo

simmsb / calamity Goto Github PK

View Code? Open in Web Editor NEW
109.0 5.0 11.0 1.39 MB

A library for writing discord bots in haskell

Home Page: https://hackage.haskell.org/package/calamity

License: MIT License

Haskell 99.15% Nix 0.76% Shell 0.09%
haskell discord discord-api discord-library discord-bot calamity polysemy

calamity's Introduction

Calamity

Hackage Build Status License Hackage-Deps Discord Invite

Calamity is a Haskell library for writing discord bots, it uses Polysemy as the core library for handling effects, allowing you to pick and choose how to handle certain features of the library.

If you're looking for something with a less complicated interface, you might want to take a look at discord-haskell.

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. An example of using prometheus as the metrics handler can be found here.

  • Logging: The di-polysemy library is used to allow the logging effect to be customized, or disabled.

Docs

You can find documentation on hackage at: https://hackage.haskell.org/package/calamity

There's also a good blog post that covers the fundamentals of writing a bot with the library, you can read it here: https://morrowm.github.io/posts/2021-04-29-calamity.html

Examples

Here's a list of projects that use calamity:

(Feel free to contact me via the discord server, or email me via [email protected] if you've written a bot using calamity, or don't want your project listed here)

#!/usr/bin/env cabal
{- cabal:
  build-depends:
     base >= 4.13 && < 5
     , calamity >= 0.10.0.0
     , optics >= 0.4.1 && < 0.5
     , di-polysemy ^>= 0.2
     , di >= 1.3 && < 2
     , df1 >= 0.3 && < 0.5
     , di-core ^>= 1.0.4
     , polysemy >= 1.5 && <2
     , polysemy-plugin >= 0.3 && <0.5
     , stm >= 2.5 && <3
     , text-show >= 3.8 && <4
     , http-client ^>= 0.7
-}

{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}

{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}

module Main (main) where

import Calamity
import Calamity.Cache.InMemory
import Calamity.Commands
import Calamity.Commands.Context (FullContext, useFullContext)
import Calamity.Interactions qualified as I
import Calamity.Metrics.Noop
import Calamity.Utils.CDNUrl (assetHashFile)
import Control.Concurrent
import Control.Monad
import Data.Foldable (for_)
import Data.Text qualified as T
import Di qualified
import DiPolysemy qualified as DiP
import Optics
import Polysemy qualified as P
import Polysemy.Async qualified as P
import Polysemy.State qualified as P
import System.Environment (getEnv)
import TextShow
import Network.HTTP.Client (RequestBody(RequestBodyLBS))

data MyViewState = MyViewState
  { numOptions :: Int
  , selected :: Maybe T.Text
  }

$(makeFieldLabelsNoPrefix ''MyViewState)

main :: IO ()
main = do
  token <- T.pack <$> getEnv "BOT_TOKEN"
  Di.new $ \di ->
    void
      . P.runFinal
      . P.embedToFinal
      . DiP.runDiToIO di
      . runCacheInMemory
      . runMetricsNoop
      . useConstantPrefix "!"
      . useFullContext
      $ runBotIO (BotToken token) defaultIntents
      $ do
        void . addCommands $ do
          helpCommand
          -- just some examples

          command @'[User] "pfp" \ctx u -> do
            Right pfp <- fetchAsset (u ^. #avatar)
            let name = maybe "default.png" assetHashFile (u ^. #avatar % #hash)
                file = CreateMessageAttachment name (Just "Your avatar") (Network.HTTP.Client.RequestBodyLBS pfp)
            void $ tell ctx file
          command @'[User] "utest" \ctx u -> do
            void . tell @T.Text ctx $ "got user: " <> showt u
          command @'[Named "u" User, Named "u1" User] "utest2" \ctx u u1 -> do
            void . tell @T.Text ctx $ "got user: " <> showt u <> "\nand: " <> showt u1
          command @'[T.Text, Snowflake User] "test" \_ctx something aUser -> do
            DiP.info $ "something = " <> showt something <> ", aUser = " <> showt aUser
          group "testgroup" $ do
            void $ command @'[[T.Text]] "test" \ctx l -> do
              void . tell @T.Text ctx $ "you sent: " <> showt l
            group "say" do
              command @'[KleenePlusConcat T.Text] "this" \ctx msg -> do
                void $ tell @T.Text ctx msg
          command @'[] "explode" \_ctx -> do
            Just _ <- pure Nothing
            DiP.debug @T.Text "unreachable!"
          command @'[] "bye" \ctx -> do
            void $ tell @T.Text ctx "bye!"
            stopBot

          -- views!

          command @'[] "components" \ctx -> do
            let view options = do
                  ~(add, done) <- I.row do
                    add <- I.button ButtonPrimary "add"
                    done <- I.button ButtonPrimary "done"
                    pure (add, done)
                  s <- I.select options
                  pure (add, done, s)
            let initialState = MyViewState 1 Nothing
            s <- P.evalState initialState $
              I.runView (view ["0"]) (tell ctx) \(add, done, s) -> do
                when add do
                  n <- P.gets (^. #numOptions)
                  let n' = n + 1
                  P.modify' (#numOptions .~ n')
                  let options = map (T.pack . show) [0 .. n]
                  I.replaceView (view options) (void . I.edit)

                when done do
                  finalSelected <- P.gets (^. #selected)
                  I.endView finalSelected
                  I.deleteInitialMsg
                  void . I.respond $ case finalSelected of
                    Just x -> "Thanks: " <> x
                    Nothing -> "Oopsie"

                case s of
                  Just s' -> do
                    P.modify' (#selected ?~ s')
                    void I.deferComponent
                  Nothing -> pure ()
            P.embed $ print s

          -- more views!

          command @'[] "cresponses" \ctx -> do
            let view = I.row do
                  a <- I.button ButtonPrimary "defer"
                  b <- I.button ButtonPrimary "deferEph"
                  c <- I.button ButtonPrimary "deferComp"
                  d <- I.button ButtonPrimary "modal"
                  pure (a, b, c, d)

                modalView = do
                  a <- I.textInput TextInputShort "a"
                  b <- I.textInput TextInputParagraph "b"
                  pure (a, b)

            I.runView view (tell ctx) $ \(a, b, c, d) -> do
              when a do
                void I.defer
                P.embed $ threadDelay 1000000
                void $ I.followUp @T.Text "lol"

              when b do
                void I.deferEphemeral
                P.embed $ threadDelay 1000000
                void $ I.followUpEphemeral @T.Text "lol"

              when c do
                void I.deferComponent
                P.embed $ threadDelay 1000000
                void $ I.followUp @T.Text "lol"

              when d do
                void . P.async $ do
                  I.runView modalView (void . I.pushModal "lol") $ \(a, b) -> do
                    P.embed $ print (a, b)
                    void $ I.respond ("Thanks: " <> a <> " " <> b)
                    I.endView ()

        react @('CustomEvt (CtxCommandError FullContext)) \(CtxCommandError ctx e) -> do
          DiP.info $ "Command failed with reason: " <> showt e
          case e of
            ParseError n r ->
              void . tell ctx $
                "Failed to parse parameter: "
                  <> codeline n
                  <> ", with reason: "
                  <> codeblock' Nothing r
            CheckError n r ->
              void . tell ctx $
                "The following check failed: "
                  <> codeline n
                  <> ", with reason: "
                  <> codeblock' Nothing r
            InvokeError n r ->
              void . tell ctx $
                "The command: "
                  <> codeline n
                  <> ", failed with reason: "
                  <> codeblock' Nothing r

Disabling library logging

The library logs on debug levels by default, if you wish to disable logging you can do something along the lines of:

import qualified Di
import qualified Df1
import qualified Di.Core
import qualified DiPolysemy

filterDi :: Di.Core.Di l Di.Path m -> Di.Core.Di l Di.Path m
filterDi = Di.Core.filter (\_ p _ -> Df1.Push "calamity" `notElem` p)

Di.new $ \di ->
-- ...
  . runDiToIO di
  -- disable logs emitted by calamity
  . DiPolysemy.local filterDi
  . runBotIO
  -- ...

Nix

If you trust me, I have a cachix cache setup at simmsb-calamity.

With cachix installed, you should be able to run cachix use simmsb-calamity to add my cache to your list of caches.

You can also just manually add the substituter and public key:

substituters = https://simmsb-calamity.cachix.org
trusted-public-keys = simmsb-calamity.cachix.org-1:CQsXXpwKsjSVu0BJFT/JSvy1j6R7rMSW2r3cRQdcuQM= 

After this nix builds should just use the cache (I hope?)

For an example of a bot built using nix, take a look at: simmsb/calamity-bot

calamity's People

Contributors

drewfenwick avatar funketh avatar innf107 avatar kaptch avatar miezhiko avatar morrowm avatar pufferffish avatar simmsb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

calamity's Issues

Support Discord slash commands

As of December 15, 2020 Discord released slash commands which allow "first party" custom command integration. This new API entails telling discord about your slash commands, the name followed by the args the command takes and their types, and Discord will mutually exclusively notify either a webbhook type service that responds with the JSON or recieve an interaction event from the gateway and then the bot must then tell Discord its response. In the context of a Discord library, I think operating via the gateway makes more sense.

Adding support for this feature will require:

  • Data types for declaring the slash commands(ApplicationCommand)
  • Data types for receiving interactions (Interaction)
  • Data types for responding to interactions (Interaction Response)
  • Create slash commands via the API (Docs, GET,POST,PATCH,DELETE /applications/{application.id}/commands, GET,POST,PATCH,DELETE /applications/{application.id}/guilds/{guild.id}/commands)
  • Handle the INTERACTION_CREATE gateway event (Docs are sparse but I believe the payload of the event would be the interaction JSON defined above and talked about in the docs just linked)
  • Respond initially to the interaction in 3s via the API (Docs, POST /interactions/{interaction.id}/{interaction.token}/callback)
  • Optionally respond additionally within 15m via the API (Docs, PATCH,DELETE /webhooks/<application_id>/<interaction_token>/messages/@original, POST /webhooks/<application_id>/<interaction_token>, PATCH /webhooks/<application_id>/<interaction_token>/messages/<message_id>)

Limitations

In this documentation you'll find some notes about limits and caps on certain parts of Slash Commands. At a high level, they are as follows:

  • An app can have up to 50 top-level global commands (50 commands with unique names)
  • An app can have up to an additional 50 guild commands per guild
  • An app can have up to 10 subcommand groups on a top-level command
  • An app can have up to 10 subcommands within a subcommand group
  • choices can have up to 10 values per option
  • commands can have up to 10 options per command
  • Limitations on command names
  • Limitations on nesting subcommands and groups

The webhook version of the API requires verifying public/private key signatures, however since this approach would probably not be used there is no need to worry about it.

To use slash commands the bots does need different permissions though, but that is on the bot creator to setup properly.

A Note on the ability to declare commands and their arguments

By being able to declare the types of the commands and the arguments it should be possible to create n high level DSL that would allow declaring the command, something like optparse-aplicative, along with a handler. Running the DSL would produce the required slash command declarations as well as a router that can take interactions and, by using the name and the arguments, route the interaction to the correct handler. In this regard, the DSL might look like servant.

More metrics

Add metric counters for command invokations, individual WS events, etc

Tutorial

A tutorial, explaining step by step the DSL would be nice.

Refactor HasID typeclass

Instead of HasID a only having a single output type, allow it to work for types that contain multiple IDs, so:

class HasID a b where
    getID :: a -> Snowflake b

instance HasID Message Message where
    getID = view #id

instance HasID Message Channel where
    getID = view #channelID

Alternative ways of parsing commands

Regex command parsing would be good, and would remove the need for 'aliases'.
Though we'd need to do some funny stuff so that we don't have to try each regex in series.

can reply but can't tell with bindSemToIO

I try to add two methods

handleFailByLogging $ do
      messageIO <- bindSemToIO messageWithSnowflake
      replyIO   <- bindSemToIO replyWithSnowflake 
      void $ P.embed
           $ forkIO
           $ runKafkaConsumer (cfg ^. #kafkaAddress)
                               messageIO replyIO
messageWithSnowflake  (BotC r)
                    => (Snowflake Channel, Text)
                    -> P.Sem r ()
messageWithSnowflake (chanId, txt) = do
  chanUpgrade <- upgrade chanId
  case chanUpgrade of
    Just chan -> void $ invoke (CreateMessage chan (def & #content ?~ txt))
    Nothing   -> pure ()

replyWithSnowflake  (BotC r, HasID Channel Message)
                  => (Snowflake Message, Text)
                  -> P.Sem r ()
replyWithSnowflake (msgId, txt) = do
  maybeMsgFromId <- getMessage msgId
  case maybeMsgFromId of
    Just msgFromId -> void $ reply @Text msgFromId txt
    Nothing        -> pure ()

also was trying with messageWithSnowflake (chan, txt) = void $ tell @Text chan txt

and with some reason replyIO is working but msgIO is not when I'm trying just to send message on channel, no error messages either but possibly I just can't see prints inside it... what is possibly going wrong or how can I find out

Sequencing of events?

This is more of a question than an issue, but is there a way to figure out which event occurred first between two events?

My use-case is that I have a bot that sends join and leave messages into a text channel whenever someone joins or leaves an associated voice channel. I have a handler which reacts to VoiceStateUpdateEvts and sends the appropriate message. The issue is that sometimes someone joins and immediately leaves a voice channel (or vice-versa). When that occurs it can sometimes happen that the leave event is handled before the join event which means the messages are sent out of order.

So my question is whether there's a way to recover such ordering information. From the Discord API I see there's some sort of sequence number but I'm not sure whether that's suitable for this.

ModifyGuildMember parameters are not nullable

According to the discord developer documentation, all fields to ModifyGuildMember should be optional and nullable.

In this implementation, all fields are represented as Maybes and are therefore optional, but cannot be set to null.

In practice this means, that disconnecting a player from a voice channel or resetting their nickname is impossible.

No HasID instances for FullContext and LightContext?

Is there a reason FullContext and LightContext don't have HasID Channel/Message/User instances? It'd be quite nice for e.g. invoke $ CreateMessage ctx $ def ... rather than having to use ctx ^. #channel all the time.
If not I'd be glad to make a PR.

No Guild Create events as guilds become available

Upon start guilds that the bot is a part of will start out as unavailable, and stay so because no Guild Create event is received to update their status. I'll investigate further, but it appears that the problem happens somewhere before handleEvent.

No way to upgrade Snowflake VoiceChannel to VoiceChannel

It seens like it's currently impossible to upgrade Snowflake VoiceChannel to VoiceChannel.
Sorry, if I just missed the intended solution.
My workaround so far was to create my own orphan instance

instance Upgradeable VoiceChannel (Snowflake VoiceChannel) where
    upgrade s = upgrade (coerceSnowflake @_ @Channel s) <&> \case
            Just (GuildChannel' (GuildVoiceChannel vc)) -> Just vc
            _ -> Nothing

Improve default help command

It's probably useful if we add an extra bit to the help output for commands
explaining what different parameters mean.

Commands cannot be added to User Token based auth

Sorry for a stupid question - haskell beginner here)
Is this the intended behaviour?
I cannot pinpoint anywhere in the control flow when this gets turned on/off. The received messages are the same that means same Context could be built from them.

GHC 9 support

I've encountered a few issues with GHC 9:
The first one is fairly straightforward:

calamity> /home/theo/prj/calamity/calamity/Calamity/Client/Types.hs:259:31: error:
calamity>     * Syntax error on 'ReadyEvt
calamity>       Perhaps you intended to use TemplateHaskell or TemplateHaskellQuotes
calamity>     * In the Template Haskell quotation 'ReadyEvt
calamity>     |
calamity> 259 |         [ WrapTypeable $ EH @ 'ReadyEvt []
calamity>     |                               ^^^^^^^^^

6fb89b0 added a space after the @ in the type application which GHC 9 doesn't seem to like.

Once I remove the spaces, I get this error here:

calamity> /home/theo/prj/calamity/calamity/Calamity/Client/Client.hs:489:48: error:
calamity>     * Couldn't match type: Lens' m0 (Maybe (IxValue m0))
calamity>                      with: (Maybe Member
calamity>                             -> Const
calamity>                                  (base-4.15.0.0:Data.Semigroup.Internal.Endo [Member])
calamity>                                  (Maybe Member))
calamity>                            -> SM.SnowflakeMap Member
calamity>                            -> Const
calamity>                                 (base-4.15.0.0:Data.Semigroup.Internal.Endo [Member])
calamity>                                 (SM.SnowflakeMap Member)
calamity>       Expected: Snowflake b0
calamity>                 -> (Maybe Member
calamity>                     -> Const
calamity>                          (base-4.15.0.0:Data.Semigroup.Internal.Endo [Member])
calamity>                          (Maybe Member))
calamity>                 -> SM.SnowflakeMap Member
calamity>                 -> Const
calamity>                      (base-4.15.0.0:Data.Semigroup.Internal.Endo [Member])
calamity>                      (SM.SnowflakeMap Member)
calamity>         Actual: Index m0 -> Lens' m0 (Maybe (IxValue m0))
calamity>     * In the first argument of `(.)', namely `at'
calamity>       In the first argument of `foldMap', namely `(at . getID)'
calamity>       In the first argument of `(.)', namely
calamity>         `foldMap (at . getID) members'
calamity>     |
calamity> 489 |   let members' = guild ^.. #members . foldMap (at . getID) members . _Just

For reference, here's the stack.yaml I used (the extra-deps are perhaps overkill, I just pasted them from my bot):

resolver: nightly-2021-05-01
allow-newer: true
compiler: ghc-9.0.1

packages:
- calamity
- calamity-commands

extra-deps:
- aeson-1.5.6.0
- assoc-1.0.2
- atomic-primops-0.8.4
- attoparsec-iso8601-1.0.2.0
- base-orphans-0.8.4
- basement-0.0.12
- bifunctors-5.5.11
- Cabal-3.4.0.0
- comonad-5.0.8
- contravariant-1.5.3
- data-fix-0.3.1
- data-flags-0.0.3.4
- df1-0.4
- di-1.3
- di-df1-1.2.1
- di-handle-1.0.1
- di-polysemy-0.2.0.0
- flexible-defaults-0.0.3
- focus-1.0.2
- foldl-1.4.11
- formatting-7.1.2
- free-5.1.7
- generic-deriving-1.14
- generic-override-0.0.0.0
- generic-override-aeson-0.0.0.0
- ghc-boot-9.0.1
- ghc-boot-th-9.0.1
- http-api-data-0.4.3
- indexed-traversable-0.1.1
- indexed-traversable-instances-0.1
- invariant-0.5.4
- kan-extensions-5.2.2
- lens-5.0.1
- lens-aeson-1.1.1
- modern-uri-0.3.4.1
- MonadRandom-0.5.3
- network-uri-2.6.4.1
- parsec-3.1.14.0
- pipes-4.3.15
- primitive-unlifted-0.1.3.0
- profunctors-5.6.2
- QuickCheck-2.14.2
- random-1.2.0
- random-source-0.3.0.11
- rvar-0.2.0.6
- semigroupoids-5.3.5
- singletons-3.0
- singletons-th-3.0
- splitmix-0.1.0.3
- StateVar-1.2.1
- stm-containers-1.2
- stm-hamt-1.2.0.6
- strict-0.4.0.1
- syb-0.7.2.1
- tagged-0.8.6.1
- template-haskell-2.17.0.0
- text-1.2.4.1
- text-show-3.9
- th-abstraction-0.4.2.0
- th-compat-0.1.2
- th-desugar-1.12
- th-expand-syns-0.4.8.0
- th-lift-0.8.2
- th-lift-instances-0.1.18
- th-orphans-0.13.11
- these-1.1.1.1
- time-compat-1.9.5
- unboxing-vector-0.2.0.0
- uuid-types-1.0.4
- websockets-0.12.7.2
- ghc-tcplugins-extra-0.4.1
- github: funketh/haskell-src-meta
  commit: fb5c21707a7cc32bc7d4bf4ec00073c3a71b5cfe
  subdirs: [haskell-src-meta]
# GHC 9 patches:
- github: andrewthad/hs-memory
  commit: 683b402965e68800337af8f9b237d28be273c0d8
- github: kcsongor/generic-lens
  commit: 8e1fc7dcf444332c474fca17110d4bc554db08c8
  subdirs:
    - generic-optics
    - generic-lens
    - generic-lens-core
- github: amesgen/cryptonite
  commit: c5b2630ac396ad2d14ee1ed5d216907acaf2e79e
- github: mokus0/th-extras
  commit: 57a97b4df128eb7b360e8ab9c5759392de8d1659
- github: hasufell/primitive-extras
  commit: c8a8fc5222ab203cf0e566c58e9483c1f231cfdd
- github: galenhuntington/fmt
  commit: 913194f8f7aab4445a82994a38c5804a31333b75
- github: mithrandi/req
  commit: f1afa23ccc6c5e580b534c8341e155b29f93353c
- github: polysemy-research/polysemy
  commit: 09c65bdae9c9ce50a55c7ee2ab9681916f09f4c3
  subdirs:
    - .
    - polysemy-plugin
- github: funketh/random-fu
  commit: 2d38294582ba211df6a5a2b7f7a2c5e71038b0e7
  subdirs:
    - random-fu

Member fields

It's rather a question than an issue. How does the Member datatype initialize a guild id field? Is it initialized somewhere inside the library? I'm trying to work on some voice chat solution, and VoiceStateUpdateData fails to be parsed from a JSON because its inner Member field fails to be parsed. I can use guild id from VoiceStateUpdateData itself, but I'm not sure if it is a good idea.

Question: how invole sendMssage on IO () scope

Trying to reply @Text message myVal inside just IO () method

I'm starting some separated polling loop the same time with a bot and want to have ability to call bot functionality such sendMessage etc there too.

     Couldn't match expected type: IO a0
                  with actual type: Polysemy.Internal.Sem
                                      r0 (Either RestError Message)

as far a I understand I still can do it with polysemy eval?

please provide minimal example

Check failure reason reporting

When command checks fail, is there any way to surface the message from the Maybe Text?

Failures can of course be reported/logged from within the checks, but some e.g. debug logging would be convenient, especially when using requiresPure.

No VoiceStateUpdate event?

The events in EventType seem to be missing a few events, most notably Voice Channel related events like VoiceStateUpdate.

Pomelo support

Discord is changing how usernames work on their platform. This is internally codenamed "Pomelo".

I haven't taken a deep look into how Calamity handles discriminators or usernames, or any potential logic related to looking up users by their usernames. Here's some important notes, though:

  • Pomelo-migrated users have a discriminator of "0". I'm not sure if this field will be outright removed after migration completes.
  • Users have an nullable global_name field, which corresponds to the "Display Name" field in the User Profile Settings.
  • Precedence as to what "name" a user has when being displayed to a human is: guild nickname > display name/global_name > username.

See: discord.py patch

Command and Group system

something that allows for constructing commands and groups in a nice way, something that looks like this maybe:

command @"ban" @'[Member, KleeneConcat Text] \member reason -> do
    print $ "banning member: " <> showt member <> ", with reason: " <> reason
    invoke $ CreateGuildBan (getID @Guild member) member def

pre-invokation checks could also be done, maybe something like:

requires [a, list, of_, checks] do
    command @"ban" @'[Member, KleeneConcat Text] \member reason -> do
        print $ "banning member: " <> showt member <> ", with reason: " <> reason
        invoke $ CreateGuildBan (getID @Guild member) member def

groups would then be similar:

group @"mod" do
    requires [a, list, of_, checks] do
        command @"ban" @'[Member, KleeneConcat Text] \member reason -> do
            print $ "banning member: " <> showt member <> ", with reason: " <> reason
            invoke $ CreateGuildBan (getID @Guild member) member def

Whether the names of groups/ commands needs to be a symbol or not I'll need to think about
it could possibly be used to prevent duplicate commands and groups at compile time I guess.

issues with the `avatar` field in User/Member models

Hello! This is a fantastic library that's engineered very nicely, great job! Though, I've found some weird implementation details here that could improve the library if changed to work well. Though, this probably won't be backwards compatible.. but I think any other architecture would still be good, as long as you can use the avatar field easily.

Firstly, the major issue; the avatar field in User (I'm unsure if it's the same for Member, but it most likely is) is just a hash. I understand the reasoning for it and that hash could, in some very rare situations, be useful by itself alone... but it is pretty much useless in the form that it is currently. I believe that the one we have currently should be made into an avatarHash field, and a new one named avatar that results in a link to the avatar of the user. There may be the need to have some sane defaults, like automatically selecting the size to be the biggest one by default, using the PNG format by default (and having an option to change both of those, it doesn't need to be in the avatar field, maybe a getAvatar function). But it boosts how useful it is.

I know that the avatar field of a member is a hash in the original API, but the original API is mostly used with other wrappers for a reason; to have a higher level interface.

But also, another thing; there's no reason for the avatar to be a Maybe Text. Technically, every single account in discord has an avatar. And yes, they can use the default avatars by not setting one or removing them, but it'd make everything more seamless if the library accounted for that by itself. You're able to make it a Text while providing more functionality.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.