Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
76e7952
add guild member add event
Candygoblen123 Jun 2, 2023
3469485
fix bug where socket was not created on macOS
Candygoblen123 Jun 2, 2023
e24b39e
edit nick test
Candygoblen123 Jun 2, 2023
43006d4
edit nixk test 2
Candygoblen123 Jun 2, 2023
c945cb9
edit nick test
Candygoblen123 Jun 2, 2023
3edba13
add role
Candygoblen123 Jun 2, 2023
75f8f01
add GetGuildRoles
Candygoblen123 Jun 2, 2023
edf168a
ensure disconnect on SIGINT
Candygoblen123 Jun 4, 2023
cfb434c
bug fix
Candygoblen123 Jun 4, 2023
f608a93
bug fix
Candygoblen123 Jun 4, 2023
d862c59
add BotGuild
Candygoblen123 Jun 4, 2023
2e315ec
Merge branch 'SwiftcordApp:main' into main
Candygoblen123 Jun 5, 2023
63f7a3b
add convenience method for loading token from file
Candygoblen123 Jun 6, 2023
36989e5
Add properties to BotMessage
Candygoblen123 Jun 6, 2023
a946b94
Block main thread to prevent exit
Candygoblen123 Jun 6, 2023
242e03d
rename Bot classes
Candygoblen123 Jun 7, 2023
214b036
Docs and Fixes
Candygoblen123 Jun 7, 2023
680200b
Member methods
Candygoblen123 Jun 7, 2023
447c5fb
Add basic classes and methods
Candygoblen123 Jun 13, 2023
bb220a2
Documentation stuff
Candygoblen123 Jun 13, 2023
c11eb29
more docs stuff
Candygoblen123 Jun 14, 2023
f07e34d
more docs
Candygoblen123 Jun 14, 2023
2f99549
guild properties
Candygoblen123 Jun 14, 2023
167ebf8
fix instance properties in TextChannel
Candygoblen123 Jun 14, 2023
98fd3f1
computed properties
Candygoblen123 Jun 15, 2023
c634661
fix incorrect bool check
Candygoblen123 Jun 15, 2023
63d889b
fix listGuildMembers
Candygoblen123 Jun 16, 2023
8c98842
use AsyncSequence for MemberList
Candygoblen123 Jun 19, 2023
f091526
Add PaginatedList<>
Candygoblen123 Jun 19, 2023
c1a7dfc
remove force try
Candygoblen123 Jun 19, 2023
96ba145
fix invalid form body
Candygoblen123 Jun 19, 2023
047c1f7
GuildBanEntry
Candygoblen123 Jun 19, 2023
472fb14
code documentation
Candygoblen123 Jun 19, 2023
164f840
fix lint errors
Candygoblen123 Jun 19, 2023
0727752
more linting fixes
Candygoblen123 Jun 19, 2023
ed237c8
add Guild class description
Candygoblen123 Jun 19, 2023
891c250
Snowflake.creationTime() now returns an optional value
Candygoblen123 Jun 28, 2023
3e22a77
switch to guard statements for login error checking
Candygoblen123 Jun 28, 2023
77a64d0
createdAt is now optional
Candygoblen123 Jun 28, 2023
c3c1b83
Fixed a crash when creating a jumpURL to a message
Candygoblen123 Jul 6, 2023
aea160d
Add additional properties to CommandData
Candygoblen123 Jul 13, 2023
499ba17
fix getting additional properties in CommandData
Candygoblen123 Jul 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/generate-docc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ env:

jobs:
generate:
runs-on: macos-12
runs-on: macos-13
env:
BUILD_DIR: _docs/

Expand All @@ -41,8 +41,8 @@ jobs:
##########################
## Select Xcode
##########################
- name: Select Xcode 14.2
run: sudo xcode-select -s /Applications/Xcode_14.2.app
- name: Select Xcode 14.3.1
run: sudo xcode-select -s /Applications/Xcode_14.3.1.app

##########################
## Cache
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ xcuserdata/
.build/
.swiftpm/
.idea/
_docs/
31 changes: 28 additions & 3 deletions Sources/DiscordKitBot/ApplicationCommand/CommandData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import DiscordKitCore
/// Provides methods to get parameters of and respond to application command interactions
public class CommandData {
internal init(
optionValues: [OptionData],
commandData: Interaction.Data.AppCommandData, interaction: Interaction,
rest: DiscordREST, applicationID: String, interactionID: Snowflake, token: String
) {
self.rest = rest
self.token = token
self.interactionID = interactionID
self.applicationID = applicationID

self.optionValues = Self.unwrapOptionDatas(optionValues)
self.optionValues = Self.unwrapOptionDatas(commandData.options ?? [])
self.interaction = interaction
}

/// A private reference to the active rest handler for handling actions
Expand All @@ -33,6 +34,8 @@ public class CommandData {

/// Values of options in this command
private let optionValues: [String: OptionData]
/// The raw command data
private let interaction: Interaction

/// If this reply has already been deferred
fileprivate var hasReplied = false
Expand All @@ -42,6 +45,27 @@ public class CommandData {
let token: String
/// The ID of this interaction
public let interactionID: Snowflake
/// The guild member that sent the interaction
public var member: Member? {
get {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get keyword should be removed here as well to resolve the following lint warning.

guard let coreMember = interaction.member, let rest = rest else { return nil }
return Member(from: coreMember, rest: rest)
}
}

public var guild: Guild? {
get async {
guard let guild_id = interaction.guildID else { return nil }
return try? await Guild(id: guild_id)
}
}

public var channel: GuildChannel? {
get async {
guard let channelID = interaction.channelID else { return nil }
return try? await GuildChannel(from: channelID)
}
}

fileprivate static func unwrapOptionDatas(_ options: [OptionData]) -> [String: OptionData] {
var optValues: [String: OptionData] = [:]
Expand Down Expand Up @@ -133,7 +157,8 @@ public extension CommandData {
/// reply in clients. However, if a call to ``deferReply()`` was made, this
/// edits the loading message with the content provided.
func followUp(content: String?, embeds: [BotEmbed]?, components: [Component]?) async throws -> Message {
try await rest!.sendInteractionFollowUp(.init(content: content, embeds: embeds, components: components), applicationID: applicationID, token: token)
let coreMessage = try await rest!.sendInteractionFollowUp(.init(content: content, embeds: embeds, components: components), applicationID: applicationID, token: token)
return await Message(from: coreMessage, rest: rest!)
}

/// Defer the reply to this interaction - the user sees a loading state
Expand Down
40 changes: 0 additions & 40 deletions Sources/DiscordKitBot/BotMessage.swift

This file was deleted.

53 changes: 53 additions & 0 deletions Sources/DiscordKitBot/BotObjects/Channels/Category.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import DiscordKitCore

/// Represents a channel category in a Guild.
public class CategoryChannel: GuildChannel {
private var coreChannels: [DiscordKitCore.Channel] {
get async throws {
try await rest!.getGuildChannels(id: coreChannel.guild_id!).compactMap({ try $0.result.get() }).filter({ $0.parent_id == id })
}
}
/// All the channels in the category.
public var channels: [GuildChannel] {
get async throws {
return try await coreChannels.asyncMap({ try GuildChannel(from: $0, rest: rest!) })
}
}
/// The text channels in the category.
public var textChannels: [TextChannel] {
get async throws {
return try await coreChannels.filter({ $0.type == .text }).asyncMap({ try TextChannel(from: $0, rest: rest!) })
}
}
/// The voice channels in the category.
public var voiceChannels: [GuildChannel] {
get async throws {
return try await coreChannels.filter({ $0.type == .voice }).asyncMap({ try TextChannel(from: $0, rest: rest!) })
}
}
/// The stage channels in the category.
public var stageChannels: [GuildChannel] {
get async throws {
return try await coreChannels.filter({ $0.type == .stageVoice }).asyncMap({ try TextChannel(from: $0, rest: rest!) })
}
}
/// If the category is marked as nsfw.
public let nsfw: Bool

override init(from channel: DiscordKitCore.Channel, rest: DiscordREST) throws {
if channel.type != .category { throw GuildChannelError.badChannelType }
nsfw = channel.nsfw ?? false

try super.init(from: channel, rest: rest)
}

/// Get a category from it's Snowflake ID.
/// - Parameter id: The Snowflake ID of the category.
/// - Throws: `GuildChannelError.BadChannelType` if the ID does not correlate with a text channel.
public convenience init(from id: Snowflake) async throws {
let coreChannel = try await Client.current!.rest.getChannel(id: id)
try self.init(from: coreChannel, rest: Client.current!.rest)
}

}
7 changes: 7 additions & 0 deletions Sources/DiscordKitBot/BotObjects/Channels/Forum.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation
import DiscordKitCore

// TODO: Implement this.
// public class ForumChannel: GuildChannel {

// }
135 changes: 135 additions & 0 deletions Sources/DiscordKitBot/BotObjects/Channels/GuildChannel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import Foundation
import DiscordKitCore

/// Represents a channel in a guild, a superclass to all guild channel types.
public class GuildChannel: Identifiable {
/// The name of the channel.
public let name: String?
/// The category the channel is located in, if any.
public var category: CategoryChannel? {
get async throws {
if let categoryID = coreChannel.parent_id {
return try await CategoryChannel(from: categoryID)
}
return nil
}
}
/// When the channel was created.
public let createdAt: Date?
/// The guild that the channel belongs to.
public var guild: Guild {
get async throws {
if let guildID = coreChannel.guild_id {
return try await Guild(id: guildID)
}
throw GuildChannelError.notAGuildChannel // This should be inaccessible
}
}

/// A link that opens this channel in discord.
let jumpURL: URL
/// A string you can put in message contents to mention the channel.
public let mention: String
/// The position of the channel in the Guild's channel list
public let position: Int?
/// Permission overwrites for this channel.
public let overwrites: [PermOverwrite]?
/// Whether or not the permissions for this channel are synced with the category it belongs to.
public var permissionsSynced: Bool {
get async throws {
if let category = try await category {
return coreChannel.permissions == category.coreChannel.permissions
}
return false
}
}
/// The Type of the channel.
public let type: ChannelType
/// The `Snowflake` ID of the channel.
public let id: Snowflake

internal weak var rest: DiscordREST?
internal let coreChannel: DiscordKitCore.Channel

internal init(from channel: DiscordKitCore.Channel, rest: DiscordREST) throws {
guard channel.guild_id != nil else { throw GuildChannelError.notAGuildChannel }
self.coreChannel = channel
self.name = channel.name
self.createdAt = channel.id.creationTime()
position = channel.position
type = channel.type
id = channel.id
self.rest = rest
self.mention = "<#\(id)>"
self.overwrites = channel.permission_overwrites
self.jumpURL = URL(string: "https://discord.com/channels/\(channel.guild_id!)/\(id)")!
}

/// Initialize an Channel using an ID.
/// - Parameter id: The `Snowflake` ID of the channel you want to get.
/// - Throws: `GuildChannelError.NotAGuildChannel` when the channel ID points to a channel that is not in a guild.
public convenience init(from id: Snowflake) async throws {
let coreChannel = try await Client.current!.rest.getChannel(id: id)
try self.init(from: coreChannel, rest: Client.current!.rest)
}
}

public extension GuildChannel {
/// Creates an invite to the current channel.
/// - Parameters:
/// - maxAge: How long the invite should last in seconds. If it’s 0 then the invite doesn’t expire. Defaults to `0`.
/// - maxUsers: How many uses the invite could be used for. If it’s 0 then there are unlimited uses. Defaults to `0`.
/// - temporary: Denotes that the invite grants temporary membership (i.e. they get kicked after they disconnect). Defaults to `false`.
/// - unique: Indicates if a unique invite URL should be created. Defaults to `true`. If this is set to `False` then it will return a previously created invite.
/// - Returns: The newly created `Invite`.
func createInvite(maxAge: Int = 0, maxUsers: Int = 0, temporary: Bool = false, unique: Bool = false) async throws -> Invite {
let body = CreateChannelInviteReq(max_age: maxAge, max_users: maxUsers, temporary: temporary, unique: unique)
return try await rest!.createChannelInvite(id, body)
}

/// Deletes the channel. See discussion for warnings.
///
/// > Warning: Deleting a guild channel cannot be undone. Use this with caution, as it is impossible to undo this action when performed on a guild channel.
/// >
/// > In contrast, when used with a private message, it is possible to undo the action by opening a private message with the recipient again.
func delete() async throws {
try await rest!.deleteChannel(id: id)
}

/// Gets all the invites for the current channel.
/// - Returns: An Array of `Invite`s for the current channel.
func invites() async throws -> [Invite] {
return try await rest!.getChannelInvites(id)
}

/// Clones a channel, with the only difference being the name.
/// - Parameter name: The name of the cloned channel.
/// - Returns: The newly cloned channel.
func clone(name: String) async throws -> GuildChannel {
let body = CreateGuildChannelRed(name: name, type: coreChannel.type, topic: coreChannel.topic, bitrate: coreChannel.bitrate, user_limit: coreChannel.user_limit, rate_limit_per_user: coreChannel.rate_limit_per_user, position: coreChannel.position, permission_overwrites: coreChannel.permission_overwrites, parent_id: coreChannel.parent_id, nsfw: coreChannel.nsfw, rtc_region: coreChannel.rtc_region, video_quality_mode: coreChannel.video_quality_mode, default_auto_archive_duration: coreChannel.default_auto_archive_duration)
let newCh: DiscordKitCore.Channel = try await rest!.createGuildChannel(guild.id, body)
return try GuildChannel(from: newCh, rest: rest!)
}

/// Gets the permission overrides for a specific member.
/// - Parameter member: The member to get overrides for.
/// - Returns: The permission overrides for that member.
func overridesFor(_ member: Member) -> [PermOverwrite]? {
return overwrites?.filter({ $0.id == member.user!.id && $0.type == .member})
}

/// Sets the permissions for a member.
/// - Parameters:
/// - member: The member to set permissions for
/// - allow: The permissions you want to allow, use array notation to pass multiple
/// - deny: The permissions you want to deny, use array notation to pass multiple
func setPermissions(for member: Member, allow: Permissions, deny: Permissions) async throws {
let body = EditChannelPermissionsReq(allow: allow, deny: deny, type: .member)
try await rest!.editChannelPermissions(id, member.user!.id, body)
}
}

enum GuildChannelError: Error {
case badChannelType
case notAGuildChannel
}
7 changes: 7 additions & 0 deletions Sources/DiscordKitBot/BotObjects/Channels/Stage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation
import DiscordKitCore

// TODO: Implement this
// public class StageChannel: GuildChannel {

// }
Loading