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
),
/// Add all the intents you want
intents: [.guildMessages, .messageContent]
)
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
),
/// Add all the intents you want
intents: [.guildMessages, .messageContent]
)
/// Make an instance like above
let bot: BotGatewayManager = ...
Task {
/// Add event handlers
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
}
}
/// Tell the manager to connect to Discord.
/// FYI, This will return _before_ the connection is fully established
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()
The way you can make sense of the library is to think of it as a direct implementation of the Discord API.
In most cases, the library doesn't try to abstract away Discord's stuff.
GatewayManager
.DiscordClient
.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 a LogHandler
which can send all your logs to Discord:
import DiscordLogger
import Logging
/// Configure the Discord Logging Manager.
DiscordGlobalConfiguration.logManager = DiscordLogManager(
httpClient: HTTP_CLIENT_YOU_MADE_IN_PREVIOUS_STEPS
)
/// Bootstrap the `LoggingSystem`. After this, all your `Logger`s will automagically start using `DiscordLogHandler`.
/// Do not use a `Task { }`. Wait before the `LoggingSystem` is bootstrapped.
await LoggingSystem.bootstrapWithDiscordLogger(
/// The address to send the logs to.
/// You can easily create a webhook using Discord client apps.
address: try .url(WEBHOOK_URL),
makeMainLogHandler: StreamLogHandler.standardOutput(label:metadataProvider:)
)
/// Make sure you haven't called `LoggingSystem.bootstrap` anywhere else, because you can only call it once.
/// For example Vapor's templates use `LoggingSystem.bootstrap` on boot, and you need to remove that.
DiscordLogManager
comes with a ton of useful configuration options.
Here is an example of a decently-configured DiscordLogManager
:
Read DiscordLogManager.Configuration.init
documentation for full info.
DiscordGlobalConfiguration.logManager = DiscordLogManager(
httpClient: HTTP_CLIENT_YOU_MADE_IN_PREVIOUS_STEPS,
configuration: .init(
aliveNotice: .init(
address: try .url(WEBHOOK_URL),
/// If nil, DiscordLogger will only send 1 "I'm alive" notice, on boot.
/// If not nil, it will send a "I'm alive" notice every this-amount too.
interval: nil,
message: "I'm Alive! :)",
color: .blue,
initialNoticeMention: .user("970723029262942248")
),
mentions: [
.warning: .role("970723134149918800"),
.error: .role("970723101044244510"),
.critical: .role("970723029262942248"),
],
extraMetadata: [.warning, .error, .critical],
disabledLogLevels: [.debug, .trace],
disabledInDebug: true
)
)
If you want to only use Discord logger and don't use the rest of DiscordBM
, you can specify DiscordLogger
as your dependency:
/// In `Package.swift`:
.product(name: "DiscordLogger", package: "DiscordBM"),
/// After bootstrapping the `LoggingSystem`, and with the configuration above, but `extraMetadata` set to `[.critical]`
let logger = Logger(label: "LoggerLabel")
logger.warning("Warning you about something!")
logger.error("We're having an error!", metadata: [
"number": .stringConvertible(1),
"statusCode": "401 Unauthorized"
])
logger.critical("CRITICAL PROBLEM. ABOUT TO EXPLODE 💥")
DiscordBM
has the ability to cache Gateway events in-memory, and keep the data in sync with Discord:
let cache = await DiscordCache(
/// The `GatewayManager`/`bot` to cache the events from.
gatewayManager: GatewayManager_YOU_MADE_IN_PREVIOUS_STEPS,
/// What intents to cache their related Gateway events.
/// This does not affect what events you receive from Discord.
/// The intents you enter here must have been enabled in your `GatewayManager`.
/// With `.all`, `DiscordCache` will cache all events.
intents: [.guilds, .guildMembers],
/// In big guilds/servers, Discord only sends your own member/presence info.
/// You need to request the rest of the members, and `DiscordCache` can do that for you.
/// Must have `guildMembers` and `guildPresences` intents enabled depending on what you want.
requestAllMembers: .enabled,
/// What messages to cache.
messageCachingPolicy: .saveEditHistoryAndDeleted
)
/// Access the cached stuff:
let aGuild = await cache.guilds[GUILD_ID]
print("Guild name is:", aGuild.name)
DiscordBM
can automatically assign a role to members when they react to a message with specific emojis:
let handler = try await ReactToRoleHandler(
gatewayManager: GatewayManager_YOU_MADE_IN_PREVIOUS_STEPS,
/// Your DiscordCache. This is not necessary (you can pass `nil`)
/// Only helpful if the cache has `guilds` and/or `guildMembers` intents enabled
cache: cache,
/// The role-creation payload
role: .init(
name: "cool-gang",
color: .green
),
guildId: THE_GUILD_ID_OF_THE_MESSAGE_YOU_CREATED,
channelId: THE_CHANNEL_ID_OF_THE_MESSAGE_YOU_CREATED,
messageId: THE_MESSAGE_ID_OF_THE_MESSAGE_YOU_CREATED,
/// The list of reactions to get the role for
reactions: [.unicodeEmoji("🐔")]
)
After this, anyone reacting with 🐔
to the message will be assigned the role.
There are a bunch more options, take a look at ReactToRoleHandler
initializers for more info.
Warning
The handler will need quite a few permissions. Namelyview messages
,send messages
&add reactions
in the channel where the message is, plusmanage roles
in the guild. These are only the minimums. If the bot is receiving 403 responses from Discord, it probably needs some more permissions as well.
The handler will:
await handler.stop()
.try await handler.restart()
.If you need to persist the handler somewhere:
configuration
, which is Codable
.onConfigurationChanged
parameter in initializers:let handler = try await ReactToRoleHandler(
.
.
.
onConfigurationChanged: { configuration in
await saveToDatabase(configuration: configuration)
}
)
DiscordBM
comes with tools to make testing your app easier.
BotGatewayManager
s using the GatewayManager
protocol so you can override your gateway manager with a mocked implementation in tests.DefaultDiscordClient
and type-erase it using the DiscordClient
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.
DiscordBM
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 might result in slight code breakage if you don't follow the instructions below.
DiscordBM
can continue to add new cases to public enums in minor versions.default:
in your switch statements, or use if case
/if case let
.DiscordBM
can continue to add new parameters to public initializers/functions in minor versions.value.map(SomeDiscordBMType.init)
.Any contribution is more than welcome. You can find me in Vapor's Discord server to discuss your ideas.
Passing the linux-integration
tests is not required for PRs because of token/access problems.
link |
Stars: 17 |
Last commit: 2 days ago |
Full Changelog: https://github.com/MahdiBM/DiscordBM/compare/v1.0.0-beta.40...v1.0.0-beta.41
Full support for all slash-command managing endpoints (create, edit, delete, overwrite etc...)
guardDecodeError
functions to decodeError
.DiscordHTTPErrorResponse.basStatusCode
-> badStatusCode
(typo)Guild.userHasPermissions.userHasPermissions
to memberHasPermissions
.Guild.userHasPermissions
functions which truly acts based on a user and not a member.ApplicationCommand
(now it's RequestBody.ApplicationCommandCreate
).DefaultDiscordClient
caching logic.Guild.___HasPermissions
functions.user-agent
to http requests.Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics