- Everything with structured concurrency and async/await. Full integration with all the latest Server-Side Swift packages.
- Connect to the Discord gateway and receive all events easily.
- Send requests to the Discord API using library's Discord client.
- Hard-typed APIs. All Gateway events have their own type and all Discord API responses can be decoded easily.
- Abstractions for easier testability.
You can see Vapor community's Penny bot as a showcase of using this library in production. Penny is used to give coins to the helpful members of the Vapor community as a sign of appreciation.
Penny is available here and you can find DiscordBM
used in the PennyBOT target.
If you're using
DiscordBM
on macOS Ventura (on either Xcode or VSCode), make sure you have Xcode 14.1 or above. Lower Xcode 14 versions have known issues that cause a lot of problems for libraries.
First you need to initialize a BotGatewayManager
instance, then tell it to connect and start using it:
Make sure you've added AsyncHTTPClient to your dependancies.
import DiscordBM
import AsyncHTTPClient
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
let bot = BotGatewayManager(
eventLoopGroup: httpClient.eventLoopGroup,
httpClient: httpClient,
token: YOUR_BOT_TOKEN,
appId: YOUR_APP_ID,
presence: .init( /// Set up bot's initial presence
/// Will show up as "Playing Fortnite"
activities: [.init(name: "Fortnite", type: .game)],
status: .online,
afk: false
),
intents: [.guildMessages, .messageContent] /// Add all the intents you want
)
/// it's important to shutdown the httpClient _after all requests are done_, even if one failed
/// libraries like Vapor take care of this on their own if you use the shared http client
try httpClient.syncShutdown()
/// Prefer to use `shutdown()` in async contexts:
/// try await httpClient.shutdown()
See the GatewayConnection tests or Vapor community's Penny bot for real-world examples.
import DiscordBM
import Vapor
let app: Application = YOUR_VAPOR_APPLICATION
let bot = BotGatewayManager(
eventLoopGroup: app.eventLoopGroup,
httpClient: app.http.client.shared,
token: YOUR_BOT_TOKEN,
appId: YOUR_APP_ID,
presence: .init( /// Set up bot's initial presence
/// Will show up as "Playing Fortnite"
activities: [.init(name: "Fortnite", type: .game)],
status: .online,
afk: false
),
intents: [.guildMessages, .messageContent] /// Add all the intents you want
)
import DiscordBM
/// Make an instance like above
let bot: BotGatewayManager = ...
/// Add event handlers
Task {
await bot.addEventHandler { event in
switch event.data {
case let .messageCreate(message):
print("GOT MESSAGE!", message)
/// Switch over other cases you have intents for and you care about.
default: break
}
}
/// If you care about library parsing failures, handle them here
await bot.addEventParseFailureHandler { error, text in
/// Handle the failure using the `error` thrown and the `text` received.
}
/// Tell the manager to connect to Discord
await bot.connect()
/// Use `bot.client` to send requests to Discord.
try await bot.client.createMessage(
channelId: CHANNEL_ID,
payload: .init(content: "Hello Everybody!")
)
}
/// If you don't use libraries like Vapor that do this for you,
/// you'll need to uncomment this line and call it from a non-async context.
/// Otherwise your executable will exit immediately after every run.
/// RunLoop.current.run()
It's usually better to send a link of your media to Discord, instead of sending the actual file.
However, DiscordBM
still supports sending files directly.
Task {
/// Raw data of anything like an image
let image: ByteBuffer = ...
/// Example 1
try await bot.client.createMessage(
channelId: CHANNEL_ID,
payload: .init(
content: "A message with an attachment!",
files: [.init(data: image, filename: "pic.png")],
attachments: [.init(index: 0, description: "Picture of something secret :)")]
/// ~~~~~~~^ `0` is the index of the attachment in the `files` array.
)
)
/// Example 2
try await bot.client.createMessage(
channelId: CHANNEL_ID,
payload: .init(
embeds: [.init(
title: "An embed with an attachment!",
image: .init(url: .attachment(name: "penguin.png"))
/// ~~~~~~~^ `penguin.png` is the name of the attachment in the `files` array.
)],
files: [.init(data: image, filename: "penguin.png")]
)
)
}
Take a look at testMultipartPayload()
in /Tests/DiscordClientTests to see how you can send media in a real-world situation.
DiscordBM
comes with tools to make testing your app easier.
- You can type-erase your
BotGatewayManager
s using theGatewayManager
protocol so you can override your gateway manager with a mocked implementation in tests. - You can also do the same for
DefaultDiscordClient
and type-erase it using theDiscordClient
protocol so you can provide a mocked implementation when testing.
To use the DiscordBM
library in a SwiftPM project,
add the following line to the dependencies in your Package.swift
file:
.package(url: "https://github.com/MahdiBM/DiscordBM", from: "1.0.0-beta.1"),
Include "DiscordBM"
as a dependency for your targets:
.target(name: "<target>", dependencies: [
.product(name: "DiscordBM", package: "DiscordBM"),
]),
Finally, add import DiscordBM
to your source code.
This library will try to follow Semantic Versioning 2.0.0, with exceptions.
These exceptions should not be a big deal depending on your code style, but can result in some slight code breakage.
- Adding enum cases.
- This is so
DiscordBM
can continue to add new cases to public enums in minor versions. - If you care about code breakage, you can't use exhaustive switch statements.
Either includedefault:
in your switch statements, or useif let
/if case let
. - See this for more info.
- This is so
- Passing initializers/functions as arguments.
- This is so
DiscordBM
can continue to add new parameters to public initializers/functions in minor versions. - If you care about code breakage, you can't write code like
value.map(SomeDiscordBMType.init)
.
Luckily, not many people do or need this anyway.
- This is so
Any contribution is more than welcome. You can find me in Vapor's Discord server to discuss your ideas.
I'm also actively looking for any new info in the Discord API, and will add them to the library as soon as I can.