Giter Club home page Giter Club logo

discord-haskell's Introduction

discord-haskell CI Status Hackage version Discord server

Build that discord bot in Haskell! Also checkout the calamity haskell library for a more advanced interface.

Documentation

Example

This is an example bot that replies "pong" to messages that start with "ping". Checkout the other examples for things like state management.

{-# LANGUAGE OverloadedStrings #-}  -- allows "string literals" to be Text
import           Control.Monad (when, void)
import           UnliftIO.Concurrent
import           Data.Text (isPrefixOf, toLower, Text)
import qualified Data.Text.IO as TIO

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

-- | Replies "pong" to every message that starts with "ping"
pingpongExample :: IO ()
pingpongExample = do
    userFacingError <- runDiscord $ def
             { discordToken = "Bot ZZZZZZZZZZZZZZZZZZZ"
             , discordOnEvent = eventHandler
             , discordOnLog = \s -> TIO.putStrLn s >> TIO.putStrLn ""
             } -- if you see OnLog error, post in the discord / open an issue

    TIO.putStrLn userFacingError
    -- userFacingError is an unrecoverable error
    -- put normal 'cleanup' code in discordOnEnd (see examples)

eventHandler :: Event -> DiscordHandler ()
eventHandler event = case event of
    MessageCreate m -> when (isPing m && not (fromBot m)) $ do
        void $ restCall (R.CreateReaction (messageChannelId m, messageId m) "eyes")
        threadDelay (2 * 10^6)
        void $ restCall (R.CreateMessage (messageChannelId m) "Pong!")
    _ -> return ()

fromBot :: Message -> Bool
fromBot = userIsBot . messageAuthor

isPing :: Message -> Bool
isPing = ("ping" `isPrefixOf`) . toLower . messageContent

Discord Server

Ask questions, get updates, request features, etc in the project discord server: https://discord.gg/eaRAGgX3bK

Official Discord Documentation

This api closley matches the official discord documentation, which lists the rest requests, gateway events, and gateway sendables.

You can use the docs to check the name of something you want to do. For example: the docs list a Get Channel API path, which translates to discord-haskell's rest request ADT for GetChannel of type ChannelId -> ChannelRequest Channel.

Open an Issue

If something goes wrong: check the error message (optional: check the debugging logs), make sure you have the most recent version, ask on discord, or open a github issue.

discord-haskell's People

Contributors

0x3alex avatar annwan avatar aquarial avatar chuahou avatar drewolson avatar elikoga avatar fwrs avatar geometer1729 avatar gregory1234 avatar harmon758 avatar hippu avatar julmb avatar l0negamer avatar marcotoniut avatar matobet avatar mdeltax avatar moredhel avatar natsukagami avatar penelopeysm avatar pixelinc avatar relrod avatar rexim avatar s3np41v avatar sorki avatar t1m0thyj avatar taksuyu avatar tiltmesenpai avatar wgaffa avatar xanderdj avatar yutotakano 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  avatar  avatar  avatar  avatar  avatar  avatar

discord-haskell's Issues

Proposal: Handle websocket closure while performing initial handshake

Heya Karl, thanks as always for the simple yet amazing library. This proposal is to implement a design decision to handle websocket closures during the gateway handshake stage.

Proposal

While waiting for Opcode Hello and Opcode Dispatch "Ready", discord-haskell should handle websocket close codes properly, similar to how it is done in eventStream:
https://github.com/aquarial/discord-haskell/blob/7a33ecf756c6b404f7201e229a71bfdbd408e873/src/Discord/Internal/Gateway/EventLoop.hs#L175-L183

Existing Code

Currently, the library closes with ConnClosed on lines 74-76 (while waiting for Hello):
https://github.com/aquarial/discord-haskell/blob/7a33ecf756c6b404f7201e229a71bfdbd408e873/src/Discord/Internal/Gateway/EventLoop.hs#L74-L76
and on lines 67-69 (while waiting for Ready):
https://github.com/aquarial/discord-haskell/blob/7a33ecf756c6b404f7201e229a71bfdbd408e873/src/Discord/Internal/Gateway/EventLoop.hs#L67-L69
The library only properly filters and handle the close codes in eventStream, which is only called after Hello and Ready was received as expected.

Impact

Our bot experiences periodic disconnection from Discord (which itself is fine, since it reconnects), but once in a while, when a disconnection happens during the handshake phase, the bot goes down for good and never retries. Specifically, we get a 4000 Close Request "Unknown Error" as the Response to Identify. This is not a fatal error, and should reconnect (docs say 4000 is worth reconnecting). This proposal would fix it.

A problem I might see is that the bot would keep rapidly retrying indefinitely if there is no internet connection (i think), resulting in perhaps high computation power, so a delay may be necessary.

Other libraries

  • discord.py
  • discord.js
    • Upon opening a websocket, it immediately registers a .on("CLOSE") callback handler, that does the checking against a list of UNRESUMABLE_CLOSE_CODES, and resumes as necessary (which means even during Hello/Ready).
  • serenity (rust)
    • Similar to python, uses a single handle_event function everywhere to get everything from Hello, Heartbeat, Dispatch, etc. If the websocket has closed, it calls handle_close_event which logs and checks as necessary, then reconnects (L474). This means it also properly reconnects upon an error during handshake.
  • discordgo
    • Does not handle close codes during Hello/Ready/Identify (it returns err, which is caught in the defer func, and then just exits without reconnecting). It treats any and ALL connection errors there as a fatal error (similar to the current discord-haskell).

Overall, all major libraries reconnect as appropriate. The golang library was interesting, but either they may not have had reports on this (since they are not as major as py/js), or they made a conscious design decision not to reconnect.

Complete rest API

There's a lot of rest endpoints, some are unfinished. I'm working on them very slowly. See examples/custom-rest for a "simplest working example" of how the rest ADT is structured.

Guild

  • CreateGuild
  • AddGuildMember
  • ModifyGuildMember
  • ModifyCurrentUserNick
  • AddGuildMemberRole
  • RemoveGuildMemberRole
  • GetGuildBan
  • CreateGuildRole
  • ModifyGuildRolePositions
  • ModifyGuildRole
  • CreateGuildIntegration
  • ModifyGuildIntegration
  • GetGuildVanityURL

Invite

  • GetInvite
  • DeleteInvite

User

  • GetUserConnections
  • CreateGroupDM

Voice

  • ListVoiceRegions

Webhook

  • CreateWebhook
  • GetChannelWebhooks
  • GetGuildWebhooks
  • GetWebhook with Maybe Token
  • ModifyWebhook with Maybe Token
  • DeleteWebhook with Maybe Token
  • ExecuteWebhook
  • ExecuteWebhookSlack
  • ExecuteWebhookGitHub

Recommended way to deploy a bot?

I know this is somewhat of a silly question - deploying has nothing to do with this library and it is up to us to deploy the bot wherever we want - but in practice, I find it that additional guidance on how to deploy a bot the easy way can mean a great deal to somebody who never deployed this kind of Haskell app before.

Personally, I just wrote simple Discord bot in NodeJS and deployed it to Heroku following a tutorial that has shown how to do it in just a couple of steps.
Would it be useful to provide similar instructions here, as "how to deploy to Heroku" section?
I would like to try rewriting the bot from NodeJS into Haskell, since normally I use Haskell, so if I get to that, I could also try writing this section and making a PR. The main thing stopping me from doing it right now is knowing that I have to do extra research on how to deploy it.

Missing color attribute for CreateEmbed

It's currently not possible as far as I'm aware to input a color for the colored sidebox of an embed message. The color attribute is present in the Embed datatype but not in the CreateEmbed datatype which is the datatype that can be used to create an embedded message.

Error when parsing an integer nonce on Create Message-event

I encountered this error when I added an another bot (https://jameslantz.net/smilebot/) to my server.

It seems that while usually nonce-values are JSON-strings, some Discord API-clients (like bots) use integers instead, which causes discord-haskell to fail when parsing their messages.

Here is an example of the error

"gateway - received parse Error - Error in $: Error in $.nonce: mzero while decoding {\"t\":\"MESSAGE_CREATE\",\"s\":12,\"op\":0,\"d\":{\"type\":0,\"tts\":false,\"timestamp\":\"2019-12-28T17:34:47.866000+00:00\",\"pinned\":false,\"nonce\":1707945642857201,\"mentions\":[],\"mention_roles\":[],\"mention_everyone\":false,\"member\":{\"roles\":[\"660129893559828481\"],\"premium_since\":null,\"nick\":null,\"mute\":false,\"joined_at\":\"2019-12-27T14:40:31.269123+00:00\",\"hoisted_role\":null,\"deaf\":false},\"id\":\"660536139672715303\",\"flags\":0,\"embeds\":[],\"edited_timestamp\":null,\"content\":\"Smile Leaderboard:\\n```hippu -- :) -- 969,576,408 smiles\\nKarvavarvas -- PogChamp -- 390,269 smiles\\nMatsu -- PogChamp -- 190,080 smiles\\nMakeke -- SmileRookie -- 13 smiles\\nFoxify -- SmileRookie -- 1 smile\\nJaagr -- SmileRookie -- 0 smiles\\nHekeke -- SmileRookie -- 0 smiles```\",\"channel_id\":\"660136978754437121\",\"author\":{\"username\":\"SmileBot 1.1\",\"id\":\"645347475435028485\",\"discriminator\":\"6549\",\"bot\":true,\"avatar\":\"dddc1411236d9f7ca43518f9af8e6718\"},\"attachments\":[],\"guild_id\":\"531604327345946636\"}}"

Update data structures so that they are complete and up to date

There are a couple data structures which don't fully match the documentation, like GuildMember, Message, and a couple of others.

It would be good to match the spec more precisely so that the full API can be used.

Additionally, it would be good if the data structures followed the order that is presented in the documentation as well.

Voice streaming

Is there any example or guideline of how to stream from & to voice channels?

Bot loses activity after a while

This might be me misunderstanding how the Discord API works, but if I set an activity for a bot on startup, after a seemingly random amount of time the activity is unset in Discord.

Add ActivityTypeCompeting?

Around last December, Discord introduced a new Activity type: competing that shows as "Competing in {name}".
The relevant documentation link is here.

Would it be possible to implement this? I can see it becoming slightly complicated because the ID is 5, skipping over 4 (custom statuses, ignored for bots for now, although may be implemented later). This means fromEnum is not going to work or has to be altered slightly in https://github.com/aquarial/discord-haskell/blob/8e1988edaf9b39cc27f44c966e16a33d1ead7a35/src/Discord/Internal/Types/Gateway.hs#L148

Crash After Connecting

It's working fine on my (small) server, but after connecting to a larger server, it crashes with GatewayExceptionEventParseError "Error in $: Error in $.channels[31]: Unknown channel type:5" "Normal event loop"

Here's the minimal example I observe the behavior with.

import Data.Text ( Text )
import qualified Data.Text.IO as T

import Discord
import Discord.Types

test :: Text -> IO ()
test secret = do
 userFacingError <- runDiscord def
   { discordToken   = secret
   , discordOnStart = const $ putStrLn "Connected"
   , discordOnEnd   = putStrLn "Disconnected"
   }
 T.putStrLn userFacingError

Example more simple

I suggest this changes:

  • In the README.md:
{-# LANGUAGE OverloadedStrings #-}  -- allows "string literals" to be Text

import Control.Monad (when, forM_, void)
import qualified Data.Text.IO as TIO

import UnliftIO.Concurrent

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

-- | Replies "pong" to every message that starts with "ping"
pingpongExample :: IO ()
pingpongExample = do userFacingError <- runDiscord $ def
                                            { discordToken = "Bot ZZZZZZZZZZZZZZZZZZZ"
                                            , discordOnEvent = eventHandler }
                     TIO.putStrLn userFacingError

eventHandler :: Event -> DiscordHandler ()
eventHandler event = case event of
       MessageCreate m -> when (not (fromBot m) && isPing m) $ do
               void $ restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")
               threadDelay (2 * 10^6)
               void $ restCall (R.CreateMessage (messageChannel m) "Pong!")
       _ -> return ()

fromBot :: Message -> Bool
fromBot = userIsBot . messageAuthor

isPing :: Message -> Bool
isPing = ("ping" ==) . messageText

In ping-pong.hs:

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

import Control.Monad (when, forM_, void)
import qualified Data.Text.IO as TIO

import UnliftIO (liftIO)
import UnliftIO.Concurrent

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

-- Allows this code to be an executable. See discord-haskell.cabal
main :: IO ()
main = pingpongExample

-- | Replies "pong" to every message that starts with "ping"
pingpongExample :: IO ()
pingpongExample = do
  tok <- TIO.readFile "./examples/auth-token.secret"

  -- open ghci and run  [[ :info RunDiscordOpts ]] to see available fields
  t <- runDiscord $ def { discordToken = tok
                        , discordOnStart = startHandler
                        , discordOnEnd = liftIO $ putStrLn "Ended"
                        , discordOnEvent = eventHandler
                        , discordOnLog = \s -> TIO.putStrLn s >> TIO.putStrLn ""
                        }
  TIO.putStrLn t

-- If the start handler throws an exception, discord-haskell will gracefully shutdown
--     Use place to execute commands you know you want to complete
startHandler :: DiscordHandler ()
startHandler = do
  Right partialGuilds <- restCall R.GetCurrentUserGuilds
  let activity = Activity { activityName = "ping-pong"
                          , activityType = ActivityTypeGame
                          , activityUrl = Nothing
                          }
  let opts = UpdateStatusOpts { updateStatusOptsSince = Nothing
                              , updateStatusOptsGame = Just activity
                              , updateStatusOptsNewStatus = UpdateStatusOnline
                              , updateStatusOptsAFK = False
                              }
  sendCommand (UpdateStatus opts)
  forM_ partialGuilds $ \pg -> do
    Right guild <- restCall $ R.GetGuild (partialGuildId pg)
    Right chans <- restCall $ R.GetGuildChannels (guildId guild)
    forM_ (take 1 $ filter isTextChannel chans)
      (\channel -> restCall $ R.CreateMessage (channelId $ channel)
                                      "Hello! I will reply to pings with pongs")


-- If an event handler throws an exception, discord-haskell will continue to run
eventHandler :: Event -> DiscordHandler ()
eventHandler event = case event of
      MessageCreate m -> when (not (fromBot m) && isPing m) $ do
        void $ restCall (R.CreateReaction (messageChannel m, messageId m) "eyes")
        threadDelay (2 * 10^(6 :: Int))

        -- A very simple message.
        void $ restCall (R.CreateMessage (messageChannel m) "Pong!")

        -- A more complex message. Text-to-speech, does not mention everyone nor
        -- the user, and uses Discord native replies.
        -- Use ":info" in ghci to explore the type
        let opts :: R.MessageDetailedOpts
            opts = def { R.messageDetailedContent = "Here's a more complex message, but doesn't ping @everyone!"
                       , R.messageDetailedTTS = True
                       , R.messageDetailedAllowedMentions = Just $
                          def { R.mentionEveryone = False
                              , R.mentionRepliedUser = False
                              }
                       , R.messageDetailedReference = Just $
                          def { referenceMessageId = Just $ messageId m }
                       }
        void $ restCall (R.CreateMessageDetailed (messageChannel m) opts)
      _ -> return ()

isTextChannel :: Channel -> Bool
isTextChannel (ChannelText {}) = True
isTextChannel _                = False

fromBot :: Message -> Bool
fromBot = userIsBot . messageAuthor

isPing :: Message -> Bool
isPing = ("ping" ==) . messageText

All those changes because:

  • 1 div 10 * 10^6 is 0, so is removed
  • forM_ is used to avoid the case
  • void is used to avoid _ <- expression
  • return instead pure
  • isPing is simplificated

I cannot make a merge request, so I suggest it in a issue.

Hello from México!

Get a GatewayExceptionEventParseError

When I restarted my bot it suddenly stopped working well. It starts up and then immediately crashes in the normal event loop. The following error is generated 'GatewayExceptionEventParseError "Error in $: Error in $: key "region" not found" "Normal event loop"' . I don't think I can give a good code example to reproduce the problem. I already tried using a different token but that wasn't the issue as I'm able to connect to the gateway without a bad auth error.

Problem with Discord.Internal.Rest.Webhook

After run cabal new-run and re-run my Haskell project this was the message:

[10 of 25] Compiling Discord.Internal.Rest.Webhook ( src/Discord/Internal/Rest/Webhook.hs, dist/build/Discord/Internal/Rest/Webhook.o, dist/build/Discord/Internal/Rest/Webhook.dyn_o )

src/Discord/Internal/Rest/Webhook.hs:88:53: error:
     Couldn't match type T.Text with Key
      Expected: [aeson-2.0.1.0:Data.Aeson.Types.Internal.Pair]
        Actual: [(T.Text, Value)]
     In the second argument of ($), namely
        [(name, val) |
            (name, Just val) <- [("username", 
                                  toJSON <$> executeWebhookWithTokenOptsUsername)]]
           <> webhookContentJson executeWebhookWithTokenOptsContent
      In the expression:
        object
          $ [(name, val) |
               (name, Just val) <- [("username", 
                                     toJSON <$> executeWebhookWithTokenOptsUsername)]]
              <> webhookContentJson executeWebhookWithTokenOptsContent
      In an equation for toJSON’:
          toJSON ExecuteWebhookWithTokenOpts {..}
            = object
                $ [(name, val) |
                     (name, Just val) <- [("username", 
                                           toJSON <$> executeWebhookWithTokenOptsUsername)]]
                    <> webhookContentJson executeWebhookWithTokenOptsContent
   |
88 |   toJSON ExecuteWebhookWithTokenOpts{..} = object $ [(name, val) | (name, Just val) <-
   |                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
cabal: Failed to build discord-haskell-1.8.8 (which is required by
exe:bot-verificador from bot-verificador-0.1.0.0). See the build log above for
details.

Thanks by advance

GUILD_BAN_ADD/REMOVE fails aeson parse

Our favorite error message strikes again!

gateway - received parse Error - Error in $: Error in $: key "id" not present                                                                                            

Here's the associated log:

gateway - received {"t":"GUILD_BAN_ADD","s":50343,"op":0,"d":{"user":{"username":"A Secret Spy","id":"550191094193848320","discriminator":"1354","avatar":null},"guild_id":"542434369554481162"}}

This also affects GUILD_BAN_REMOVE. PR incoming to fix the issue

I can't use CreateMessageDetailed

Hi,

When I try:

responseWithDetailedMessage::Message->DiscordHandler ()
responseWithDetailedMessage m = do
  let opts :: R.MessageDetailedOpts
      opts = def {
        R.messageDetailedContent = "Hey @" <> (userName $ messageAuthor m),
        R.messageDetailedAllowedMentions =
          Just $ def {
            R.mentionUsers = True,
            R.mentionUserIds = [userId $ messageAuthor m]
          }
      }
  m' <- restCall $ R.CreateMessageDetailed (messageChannel m) opts
  liftIO $ print m'

the output is:

Left (RestCallErrorCode 400 "Bad Request" "{\"allowed_mentions\": [\"parse:[\\\"users\\\"] and users: [ids...] are mutually exclusive.\"]}")

Did I make a mistake?

Thanks.

Discord channel

As planned, maybe have yourself a discord channel for this library in discord api?

Ta!

Bot crashes when confronted with a server containing a Stage

Stages are a new Discord feature, and it is a new type of channel (id 13, I believe).

If there is such a channel in any server which the discord bot is in, it will not successfully handshake with the Discord API.

Two related lines get printed out.

One includes response to Identify must be Ready (it is the exact message mentioned in #43)
The other includes GatewayExceptionEventParseError "Error in $: Error in $.channels[31]: Unknown channel type:13" "Normal event loop" which is the exact message in #40 except replacing unknown channel id 5 with 13.

The required fix is, I think, to add a new type of channel which represents Stages, with id 13, to the discord-haskell API client.

ChannelGuildCategory missing some major fields

The fields available in the current ChannelGuildCategory ADT are quite limited currently, as seen here: https://github.com/aquarial/discord-haskell/blob/ffe46538c508f498b7cd98367c2aec16ede7e8e7/src/Discord/Internal/Types/Channel.hs#L117-L120

This raises some inconsistencies, as R.ModifyChannel can modify positions and permission overwrites of both channels and categories, while it is only possible to retrieve them for normal channels through R.GetChannel.

Discord's blog post on categories highlights that categories can have permission overwrites and positions. More concretely, the rightmost column in the table in the relevant docs show that all types of channels have permission_overwrites and position. Although this seems misleading since DM channels for one don't have those, I think we can be sure that category channels would have those fields.

Could these be implemented?

Regarding `MessageUpdate`

As per the changelog:

MessageUpdate does not contain a full Message object, just ChannelId MessageId

How then is one supposed to get the contents of the updated message?

Storing states between handler invocations

Let's say I have an event handler:

eventHandler :: Event -> DiscordHandler ()
eventHandler event = case event of
    MessageCreate m -> let author = messageAuthor m in {- store the author in a list -}
    _ -> pure ()

How would I save data, so that it persists between event handler invocations?

Crash on startup due to null parent_id on channel

I'm getting the following error when I try to start my bot up:

GatewayExceptionEventParseError "Error in $: Error in $.channels[0]['parent_id']: mzero" "Normal event loop"

in the logged JSON that fails to parse this is element 0 in channels

      {
        "type": 0,
        "topic": null,
        "rate_limit_per_user": 0,
        "position": 0,
        "permission_overwrites": [],
        "parent_id": null,
        "nsfw": false,
        "name": "general",
        "last_pin_timestamp": "2020-01-01T00:18:32.457000+00:00",
        "last_message_id": "745156032938115082",
        "id": "138467465867821057"
      }

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

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?

Timed messages and activities.

I'm trying to implement a bot that helps with running an auction in a discord channel. I figured out how to add state to the bot with the help of an MVar and the eventHandler. But I don't see an entry point where I send messages that say stuff like 5 seconds remaining, and so forth. The only entry point is reacting to events. I could make it so that whenever any event happens the bot checks the time and pushes messages if necessary but this doesn't feel reliable or consistent for holding an auction.

Edit message returning rest error

Running this simple line on a message returns a rest error

res <- restCall h $ R.EditMessage (messageChannel m, messageId m) "changed" Nothing
    print res
Left (RestCallErrorCode 400 "Bad Request" "{\"_misc\": [\"Expected \\\"Content-Type\\\" header to be one of {'application/json'}.\"]}")

Use monad transformers

It's sensible to use a ReaderT DiscordHandle IO monad stack instead of just IO in many places in this library to avoid passing around the DiscordHandle everywhere.

Currently in my application I've got a module specifically dedicated to wrapping this library to use this monad transformer stack and I'd love a future where the discord-haskell library itself contained this code so my application didn't have to.

Random crash after connecting

This is frustratingly difficult to reproduce. I have multiple instances of the same minimal program connected to the same server, and it takes anywhere from a couple hours to a few days for one to crash. And they don't all crash at the same time. The message it gives is GatewayExceptionUnexpected (InvalidSession False) "Response to Identify must be Ready". Using the following minimal program.

import Data.Text ( Text )
import qualified Data.Text.IO as T

import Discord

test :: Text -> IO ()
test secret = do
 userFacingError <- runDiscord def
   { discordToken   = secret
   , discordOnStart = const $ putStrLn "Connected"
   , discordOnEnd   = putStrLn "Disconnected"
   }
 T.putStrLn userFacingError

Sending local files.

I've been trying to send a message containing a local image, however, CreateMessageUploadFile wants bytestring while if I open an image as bytestring it will be IO bytestring. Is there a way around this or is uploading the images to Imgur the simplest solution?

best way to get message that was deleted?

i'm trying to use the data of a message once it's deleted, but it's proving rather difficult. i don't see messages in the cache, and i also can't use GetChannelMessage with a deleted message. any ideas how i could build on the cache or make my own? thanks in advance.

upgrade to lts-13.8

I am using lts-13.8 and cannot add this lib as a dependency as is, so have forked and upgraded to lts-13.8 (pull-req). Is it possible for discord-haskell to support both: lts-12.10 and 13.8 (possibly others) snapshots?

R in ping pong example

Thank you very much for the merge!

Commit 9a3492b unfortunately breaks the example ping-pong bot because the ADT is defined within the Discord.Requests module and not Types. Sorry for the confusing behaviour!

Library documentation

Hello,

Just wanted to know if you were planning to do either a wiki page or a little documentation website to make the lib easier to use.
If not (which is understandable as this takes time), are you planning to maybe create a discord for the community ?
If such things already exists, sorry that I didn't found them.

Use PostgreSQL DB?

is there an easy way to use this library with postgres? then it would be possible run a bot on heroku

Allow overriding rate limits for certain routes

Recently I've encountered an issue that adding reaction route was very slow (1 reaction / 1s). I've checked the rate limits, and they were indeed lower than that at 1 reaction / 0.25s. Looking at what's going wrong I found out that rate limit reset time in X-Ratelimit-Reset is in seconds and if the rate limit is lower than a second it is rounded up to the closest second.

Apparently this issue is "won't fix": discord/discord-api-docs#182

I do not propose to hardcode the rate limits into the library for this particular route (even though that's many libraries do as far as I'm aware), but some way to manually override them would be nice.

ModifyGuildRolePositions results in 400 Bad Request

I’m calling ModifyGuildRolePositions, and getting this back:

Your code threw an exception:

GuildRequest [Role]: RestCallErrorCode 400 "BAD REQUEST" "{\"_misc\": [\"Only dictionaries may be used in a DictType\"]}"

Event error: GatewayExceptionEventParseError: key "author" not present

Hi there! Recently I made a simple bot using this library, which I based off of the PingPong example. Basically it just waits until it receives a discord message prefixed with a bot command, and then parses some text from a special channel to accumulate data.

However, it seems as though randomly(sometimes after many hours, sometimes after a few minutes) I get hit with a cryptic error message that I'm not sure how to solve:

Event error: GatewayExceptionEventParseError "Error in $: Error in $: key \"author\" not present" "Normal event loop"

This seems to occur in the 'loop' part of my bot, the full source of which can be found here(pardon the mess):
(source code redacted)

It might very well be that I'm missing something obvious, but I spent some time reading through Discord's documentation and the library source and nothing stuck out to me as immediately wrong. I also can't figure out what causes this to happen. It doesn't seem to happen if I issue any commands to the bot, simply 'at random' while it is simply receiving new events while sitting idle in a server with about 40 people and a few other bots

EDIT: I'm using the latest git master of discord-haskell, and I am using GHC 8.6.3

4003 Not Authenticated after a few hours of uptime

Since updating the package to 1.8.2, our Discord bot experiences a 4003 connection error and goes offline after a few hours of uptime (usually around 3-4 hours, although this is from subjective experience).

The log prints the following:

GatewayExceptionConnection (CloseRequest 4003 "Not authenticated.") "Normal event loop close request"

Image Version

The error is fully reproducible by leaving the the example ping-pong bot running for several hours.

I believe this is somehow related to the changes made when fixing #60. Considering this is quite a serious issue, I hope it is addressed quite soon - I've tried looking into the code, but networking is a little out of my scope and I couldn't exactly figure out what was causing the problem.

DeleteGuildRole results in Aeson parse error: expected Role, encountered Array

Having called DeleteGuildRole, I’m getting this back:

Your code threw an exception:
                                           
GuildRequest Role: RestCallErrorCode 400 "Library Stopped Working"
  "Parse Exception Error in $: expected Role, encountered Array for \"[]\""

So I’m guessing this endpoint doesn’t return a Role back, just an empty [] (≈ Haskell’s unit type)?

CreateEmbedVideo?

Looking at the API, CreateEmbedImage is used to upload images but there's no counterpart for videos. I'm writing a bot that uploads short animations so I would love to see this functionality.

Non-blocking requests

Say I have a bot that both sends a message and adds a reaction. Right now, that requires two round trips. With non-blocking requests, it would only require a single round trip and thus have less latency.

Proposal: Cache User Requests

I am writing a chat bot that uses DMs as its main interaction. I was facing the problem that a message sent to a bot from a DM was not in the cache's DM channels map. As a result I had to write this snippet of code

isDM :: ChannelId -> DiscordHandle -> IO Bool
isDM ch dh = do
  c <- readCache dh
  case M.lookup ch (_dmChannels c) of
    Just chan ->
      if isDMChannel chan
        then pure True
        else pure False
    _ ->
      putStrLn "not in cache" >>
      (isDMChannel <$> (fromRight' <$> restCall dh (R.GetChannel ch)))
  where
    isDMChannel (ChannelDirectMessage {}) = True
    isDMChannel _ = False

Testing my code results in the cache missing every time requires a call to Discord to query the same channel id.

I propose that we add a updateCache :: Cache -> a -> Cache function to the Request typeclass. The default implemention would return the cache, but requests that can be cached save their result.

What do you think about this proposal? I'm not too familiar with Discord's api but is there concerns of creating an invalid cache?

Thread blocked indefinetly in an MVar operation

^ Title
Using GHC: 8.4.3

I get the error in the title when trying to run the example codes.

Code:

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Control.Monad
import Control.Exception
import Data.Char (isSpace, toLower)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO

import Discord

main :: IO ()
main = do
    tok <- T.filter (not . isSpace) <$> TIO.readFile "auth-token.secret"
    dis <- loginRestGateway (Auth tok)

    finally (forever $ do
                e <- nextEvent dis
                case e of
                    MessageCreate m -> when (isPing (messageText m)) $ do
                        resp <- restCall dis (CreateMessage (messageChannel m) "Pong!" Nothing)
                        putStrLn $ show resp

                    _ -> pure ())
            (stopDiscord dis)

isPing :: T.Text -> Bool
isPing = T.isPrefixOf "ping" . T.map toLower

I'm slowly learning Haskell so I don't really know what's going on other than it's probably in the lib or something.
Lmk if there's any more info you need or files (like the stack or cabal)

GuildMemberAdd and GuildMemberRemove is not working

Hi, it's me, again.

When I tried this::

eventHandler::Event->DiscordHandler ()
eventHandler event = do
  liftIO $ putStrLn ""
  liftIO $ print event
  liftIO $ putStrLn ""

GuildMemberAdd and GuildMemberRemove was not detected :C

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.