The madness behind the curtain: a deep dive on SwiftyBot and SwiftyLink.
2021 has been a weird year. It was the year where, in 2020, we believed we would get back to normal. Sadly, that has not happened yet, and we spent our summer in various ways. Some traveled. Some cooked. Others coded - and I am one of those people. Flooded with a backlog of projects that I have been itching to restart, I started working furiously on those old projects in late May to early June. Working as furiously as I have was never a shock to anyone, nor was it a wake-up-call to my skills and talents - it was simply my way of disconnecting from the complete madness that society is nowadays. Inspired by my work on StormBeatz, I later decided that it was time for a new, fresh project - one that combined three of my favourite things: music, discord bots, and the Swift programming language. I bet you can already tell that this will be an adventure - and I do not disagree. This was, quite simply, one of the hardest projects I have ever tried to finish, and the crazy thing is: I am not done with it yet. Follow me as I document my journey and all the wack stuff I have had to put up with.
Backstory
StormBeatz was a massive success. Two of my best friends ( and a designer who shall not be named :] ) have worked away making something that I thought was impossible - a high quality, fully fledged music bot. This motivated me to start something that I thought would help many people, and that tiny side project grew into something I am working on daily.
I have coded in Swift for the past ~6 years, and it is a language that I deeply love, even with its flaws. The thing is, being a compiled language, it has been meant, and is catered, as an Apple programming language, specifically for their platform. However, you can use Swift in any way as you desire. This project motivated me to push boundaries, and it has taught me to do things that I thought I would never do. Creating a project that interacted with an API through a library that was not fully developed (more later on), and another project that communicated with a server over a WebSocket that then communicated back with another project? Two years ago, I would've laughed in your face, but here we are.
I wanted to be the first to create these projects, and even though they are small, I believe that I have accomplished that goal - and I am nowhere close to done yet.
I have coded in Swift for the past ~6 years, and it is a language that I deeply love, even with its flaws. The thing is, being a compiled language, it has been meant, and is catered, as an Apple programming language, specifically for their platform. However, you can use Swift in any way as you desire. This project motivated me to push boundaries, and it has taught me to do things that I thought I would never do. Creating a project that interacted with an API through a library that was not fully developed (more later on), and another project that communicated with a server over a WebSocket that then communicated back with another project? Two years ago, I would've laughed in your face, but here we are.
I wanted to be the first to create these projects, and even though they are small, I believe that I have accomplished that goal - and I am nowhere close to done yet.
SwiftyLink, the bane of my existence
Okay, I don't really mean that, but this project was HELL to complete. First, I should explain what Lavalink is: it is an audio sending node based on Lavaplayer. It allows for sending audio without it ever reaching your shards. This takes a load off of the bot. But, because no Lavalink client exists for Swift (but one exists for Go...????), I decided to create one.
First, we have to establish a connection to the server. Lavalink communicates over WebSockets, so we have to tread pretty lightly. I found that Apple's URLSessionWebSocketTask works better than the likes of Starscream, so I opted to use that. Three security headers must be provided: The password (in your application.yml file) for the Authorization header, your bot's user ID for the User-Id header, and the number of shards your bot is operating on for the Num-Shards header. After that, a connection should successfully open on "localhost:2333" - you can test if it's available by going to that address in your browser - it should return a 401 error, and in your lavalink terminal window it should say authentication failed. Success!
Now, we have to send a message to get Lavalink to connect to the voice servers. We have to stitch together two events: the VoiceStateUpdate and the VoiceServerUpdate events. the "event" part of the JSON object will hold the voiceServerUpdate data, and the rest above the event part (guildId and sessionId) will come from the voice state of the member. We have to package these two events together into one JSON object - which we will then send to Lavalink (BEFORE WE DO: the message MUST be stringified. Lavalink will not accept raw data). This was the hardest part of creating SwiftyLink, mostly because the library had no way of receiving raw events and passing it to the end user. I had to fork the library and add that support for myself (I recommend using my library fork for SwiftyLink. I will later figure out a way to add it to the base library).
Next, we have to hijack the client and send a payload to connect to the voice channel (as Lavalink is already connected to the Voice Server). We have to do this as opposed to connecting to the voice channel using the library's built in way. We will model the json object after a normal gateway payload: the Guild ID, channel ID will be provided, as well as selfDeaf and selfMute (which will mute or deafen the bot upon joining). We will also attach the OPCode to the object (4 for VoiceStateUpdate), and send the payload to the guild's shard. We are finally connected to both the voice channel and the voice servers.
We have to strictly create structures that conform to the Codable protocol so we can send and receive messages from Lavalink. We can now send a play JSON object to Lavalink to queue a track. The string MUST be a base64 track sourced from Lavalink's REST API. These structures got so big that I had to create a separate folder and file for it (Structures/Structures.swift is 200 lines long!). After I created the play object, the rest of the objects were a breeze, as they all followed - more or less - the same format.
SwiftyLink is still very much a work in progress - I have lots more to do with it, including setting up equalizer control, sending statistics to Lavalink, and receiving/decoding more events (a majority of them are empty). I plan on releasing version 0.4.0 of SwiftyLink very soon, with the base structure for playing music all set up.
SwiftyBot, what's with the Swift?
I am a HUGE fan of Swift, as mentioned above. SwiftyBot actually was meant to be more of a moderation bot, but I got very lazy and decided it was not worth it.
When I first started creating the bot, there was ZERO examples. Not on GitHub, not in the DiscordAPI server, it was just nothing nowhere. I originally meant to use the Sword discord library, however a few days after I started, the library was archived - I am still not fully sure why. However, a library switch means great things, as SwiftDiscord has full support for voice, and it is more "developer"-oriented, as it was in a pretty rough state.
I had three main goals;
1. Create a functioning command and event handler
2. Implement similar features that my JavaScript discord bot has
3. Utilize music somehow with the voice capability.
We should start with the first goal; a functioning command and event handler. My JS bot (ModBot) utilized modules, however we don't necessarily have that.
One thing that every bot developer knows: every command needs four basic things:
1. The name of the command (also known as the command trigger)
2. The description of the command, used for the help command functions
3. The basic function of the command that will execute upon the trigger
4. The arguments of the command that represent the message content
5. Optionally, an allowed variable holding the IDs authorized to use the command.
These four things make up the basic command structure. I first had to create a command object that will be applied to all these commands.
Next, I needed a way to handle the commands. Obviously, we wouldn't want bots running commands, so we have to immediately return if the author is a bot. We also need to make sure that the author is running the command with a prefix, so we will use a guard let statement to get the prefix, and we will return if the prefix is absent, or if the given prefix does not match the prefix in our configuration file. Next, we need to grab the message_content and treat it as command arguments. We will split the message content's array into substrings, and use it for our command inputs. If the request count is equal or greater than 1, we will grab the command from the array of commands set up in the main file, and execute the command.route() function, with the message and the request as the command arguments.
Our final message event will look like this:

We can now create a Commands folder and create individual files for every command, to be organized. Every command will now be an extension of the Command structure that we previously defined.
Let's create an eval command, which will be similar to the eval command found in most discord bots. There will be major differences between the swift eval implementation, and the javascript eval implementation. Because Swift is a compiled-before-execution language and not a just-in-time-compiled language (e.g. JavaScript), we have to utilize the REPL (Read, Eval, Print, Loop) software bundled with Swift. Unlike eval, where you can easily import libraries (e.g. const Discord = require("discord.js")), we can only use the core Foundation/Dispatch/etc libraries. We can set up a Process and assign a variable to it (task), and set the launch path to "usr/bin/swift" and the argument "repl". We will join the arguments together and use that string as the input pipe. After that, we can start constructing the Embed with both the input and code output, and colour it using markdown syntax.
The end result will look like this:
Next, let's create a music handler with SwiftyLink. First, we need to initialize the Lavalink node. An example of how to do this is put in the README.md of the repository. We will need to create two files: a queue system and a manager for the queue. For this project, we will call the queue system the "ServerQueue", because every guild will have its own individual queue, instead of every guild sharing a central system. This allows for more control and customization than a shared queue would.
The ServerQueue will handle communication with Lavalink, however you are able to override it by connecting directly to the player - and then, to the node controlling the player.
First, let's construct the ServerQueue. We will need properties for the class, including the most important one: the top-level queue. We will use an Array<trackResponse>() for the queue, so we can easily add titles to it. We will add more properties, including the URL, image, base64 lavalink string, the song author, the name of the song, and more - all of this is pulled directly from the trackResponse object. We can also add a songIndex variable, and initialize it with the value 0 - this will be used later. Other properties added will be the client, the player, and the node controlling the player.
Next, we can start talking to Lavalink. We will first make a method that searches Lavalink's API for a track. We can accomplish this by using the player.search() function, with the query as a string and the songHandler as a completionHandler. This will load the song onto Lavalink, and it will add it to the top-level queue we defined earlier.
Once the track is added to the queue (using the .append() function), we will .play() the track IF the queue is currently empty. Otherwise, it will be added to the back of the queue and it will wait its turn. For the play() function, we will add the properties of the trackResponse to the ServerQueue properties (URL, name, author, etc), and send a play JSON to lavalink with the base64 track string that we sourced earlier. This will successfully send the audio through Discord's voice servers.
Finally, we have to configure the queue handler. For simplicity, we can call this the MusicHandler.
First, we have to assign a ServerQueue to each guild - we will use a dictionary to accomplish this. "public var players = [GuildID : ServerQueue?]()" works. We will create two functions: the first one will create a serverQueue. The function params will be the guild ID, the channel ID, and the player. Each will be passed to the ServerQueue class initializer (along with one extra param: the Node, however we will grab this from the Player parameter). With this serverQueue, we will append it to the dictionary, with the guildID as the key - and then, we will return it.
I mentioned a second function: the destroyPlayer function. This will, as you can probably tell, destroy the queue and remove it from the dictionary. For this, we will just use the guildID as a parameter. First, we have to clear the queue if one is currently playing. Then we can remove the value (ServerQueue), using the key (GuildID). Finally, we will return it.
With that, we have successfully created the ServerQueue. We can now add a .music property to the bot's class, and the MusicHandler() class as the value. In the join command, we will create a lavalink player from the node, connect that player, and then pass it to the MusicHandler so a ServerQueue can be created. Afterwards, we can access it using "SwiftBot.music.players[message.channel?.guild?.id]."
I plan on adding other features to SwiftyBot, including sound effects, databases, and more. I am far from done... I have more tricks up my sleeve.
Conclusion
Now that both projects are successfully up and running, I have to ask myself: what have I accomplished, and whatever I did accomplish, did I successfully complete my goals? This answer is a bit complicated, but also an easy one:
Yes. I accomplished most of my goals. I learned a lot about the Swift ecosystem, and I became more accustomed to WebSockets and the idea behind them. However, I have experienced a lot of burnout from ideas not working - which is a downside when you literally have zero examples and documentation to follow. I was so furiously into this project, that I became a bit depressed when things didn't live up to what I believed they would, but that is what happens when you dedicate so much time into your work.
Thank you all for following me on this journey. I have learned so very much from this whole ordeal, and I hope you also learned a lot.
Follow me on Twitter to stay caught up on my adventures, visit my website to learn a bit more about me, and keep an eye out on this blog for more wacky adventures coming soon - but most importantly, watch out on my GitHub for the eventual open source of SwiftyLink and an example bot - coming very very soon.
SwiftyBot, what's with the Swift?
I am a HUGE fan of Swift, as mentioned above. SwiftyBot actually was meant to be more of a moderation bot, but I got very lazy and decided it was not worth it.
When I first started creating the bot, there was ZERO examples. Not on GitHub, not in the DiscordAPI server, it was just nothing nowhere. I originally meant to use the Sword discord library, however a few days after I started, the library was archived - I am still not fully sure why. However, a library switch means great things, as SwiftDiscord has full support for voice, and it is more "developer"-oriented, as it was in a pretty rough state.
I had three main goals;
1. Create a functioning command and event handler
2. Implement similar features that my JavaScript discord bot has
3. Utilize music somehow with the voice capability.
We should start with the first goal; a functioning command and event handler. My JS bot (ModBot) utilized modules, however we don't necessarily have that.
One thing that every bot developer knows: every command needs four basic things:
1. The name of the command (also known as the command trigger)
2. The description of the command, used for the help command functions
3. The basic function of the command that will execute upon the trigger
4. The arguments of the command that represent the message content
5. Optionally, an allowed variable holding the IDs authorized to use the command.
These four things make up the basic command structure. I first had to create a command object that will be applied to all these commands.
Next, I needed a way to handle the commands. Obviously, we wouldn't want bots running commands, so we have to immediately return if the author is a bot. We also need to make sure that the author is running the command with a prefix, so we will use a guard let statement to get the prefix, and we will return if the prefix is absent, or if the given prefix does not match the prefix in our configuration file. Next, we need to grab the message_content and treat it as command arguments. We will split the message content's array into substrings, and use it for our command inputs. If the request count is equal or greater than 1, we will grab the command from the array of commands set up in the main file, and execute the command.route() function, with the message and the request as the command arguments.
Our final message event will look like this:

We can now create a Commands folder and create individual files for every command, to be organized. Every command will now be an extension of the Command structure that we previously defined.
Let's create an eval command, which will be similar to the eval command found in most discord bots. There will be major differences between the swift eval implementation, and the javascript eval implementation. Because Swift is a compiled-before-execution language and not a just-in-time-compiled language (e.g. JavaScript), we have to utilize the REPL (Read, Eval, Print, Loop) software bundled with Swift. Unlike eval, where you can easily import libraries (e.g. const Discord = require("discord.js")), we can only use the core Foundation/Dispatch/etc libraries. We can set up a Process and assign a variable to it (task), and set the launch path to "usr/bin/swift" and the argument "repl". We will join the arguments together and use that string as the input pipe. After that, we can start constructing the Embed with both the input and code output, and colour it using markdown syntax.
The end result will look like this:
Next, let's create a music handler with SwiftyLink. First, we need to initialize the Lavalink node. An example of how to do this is put in the README.md of the repository. We will need to create two files: a queue system and a manager for the queue. For this project, we will call the queue system the "ServerQueue", because every guild will have its own individual queue, instead of every guild sharing a central system. This allows for more control and customization than a shared queue would.
The ServerQueue will handle communication with Lavalink, however you are able to override it by connecting directly to the player - and then, to the node controlling the player.
First, let's construct the ServerQueue. We will need properties for the class, including the most important one: the top-level queue. We will use an Array<trackResponse>() for the queue, so we can easily add titles to it. We will add more properties, including the URL, image, base64 lavalink string, the song author, the name of the song, and more - all of this is pulled directly from the trackResponse object. We can also add a songIndex variable, and initialize it with the value 0 - this will be used later. Other properties added will be the client, the player, and the node controlling the player.
Next, we can start talking to Lavalink. We will first make a method that searches Lavalink's API for a track. We can accomplish this by using the player.search() function, with the query as a string and the songHandler as a completionHandler. This will load the song onto Lavalink, and it will add it to the top-level queue we defined earlier.
Once the track is added to the queue (using the .append() function), we will .play() the track IF the queue is currently empty. Otherwise, it will be added to the back of the queue and it will wait its turn. For the play() function, we will add the properties of the trackResponse to the ServerQueue properties (URL, name, author, etc), and send a play JSON to lavalink with the base64 track string that we sourced earlier. This will successfully send the audio through Discord's voice servers.
Finally, we have to configure the queue handler. For simplicity, we can call this the MusicHandler.
First, we have to assign a ServerQueue to each guild - we will use a dictionary to accomplish this. "public var players = [GuildID : ServerQueue?]()" works. We will create two functions: the first one will create a serverQueue. The function params will be the guild ID, the channel ID, and the player. Each will be passed to the ServerQueue class initializer (along with one extra param: the Node, however we will grab this from the Player parameter). With this serverQueue, we will append it to the dictionary, with the guildID as the key - and then, we will return it.
I mentioned a second function: the destroyPlayer function. This will, as you can probably tell, destroy the queue and remove it from the dictionary. For this, we will just use the guildID as a parameter. First, we have to clear the queue if one is currently playing. Then we can remove the value (ServerQueue), using the key (GuildID). Finally, we will return it.
With that, we have successfully created the ServerQueue. We can now add a .music property to the bot's class, and the MusicHandler() class as the value. In the join command, we will create a lavalink player from the node, connect that player, and then pass it to the MusicHandler so a ServerQueue can be created. Afterwards, we can access it using "SwiftBot.music.players[message.channel?.guild?.id]."
I plan on adding other features to SwiftyBot, including sound effects, databases, and more. I am far from done... I have more tricks up my sleeve.
When I first started creating the bot, there was ZERO examples. Not on GitHub, not in the DiscordAPI server, it was just nothing nowhere. I originally meant to use the Sword discord library, however a few days after I started, the library was archived - I am still not fully sure why. However, a library switch means great things, as SwiftDiscord has full support for voice, and it is more "developer"-oriented, as it was in a pretty rough state.
I had three main goals;
1. Create a functioning command and event handler
2. Implement similar features that my JavaScript discord bot has
3. Utilize music somehow with the voice capability.
We should start with the first goal; a functioning command and event handler. My JS bot (ModBot) utilized modules, however we don't necessarily have that.
One thing that every bot developer knows: every command needs four basic things:
1. The name of the command (also known as the command trigger)
2. The description of the command, used for the help command functions
3. The basic function of the command that will execute upon the trigger
4. The arguments of the command that represent the message content
5. Optionally, an allowed variable holding the IDs authorized to use the command.
These four things make up the basic command structure. I first had to create a command object that will be applied to all these commands.
Our final message event will look like this:

Let's create an eval command, which will be similar to the eval command found in most discord bots. There will be major differences between the swift eval implementation, and the javascript eval implementation. Because Swift is a compiled-before-execution language and not a just-in-time-compiled language (e.g. JavaScript), we have to utilize the REPL (Read, Eval, Print, Loop) software bundled with Swift. Unlike eval, where you can easily import libraries (e.g. const Discord = require("discord.js")), we can only use the core Foundation/Dispatch/etc libraries. We can set up a Process and assign a variable to it (task), and set the launch path to "usr/bin/swift" and the argument "repl". We will join the arguments together and use that string as the input pipe. After that, we can start constructing the Embed with both the input and code output, and colour it using markdown syntax.
The end result will look like this:
The ServerQueue will handle communication with Lavalink, however you are able to override it by connecting directly to the player - and then, to the node controlling the player.
First, let's construct the ServerQueue. We will need properties for the class, including the most important one: the top-level queue. We will use an Array<trackResponse>() for the queue, so we can easily add titles to it. We will add more properties, including the URL, image, base64 lavalink string, the song author, the name of the song, and more - all of this is pulled directly from the trackResponse object. We can also add a songIndex variable, and initialize it with the value 0 - this will be used later. Other properties added will be the client, the player, and the node controlling the player.
Next, we can start talking to Lavalink. We will first make a method that searches Lavalink's API for a track. We can accomplish this by using the player.search() function, with the query as a string and the songHandler as a completionHandler. This will load the song onto Lavalink, and it will add it to the top-level queue we defined earlier.
Once the track is added to the queue (using the .append() function), we will .play() the track IF the queue is currently empty. Otherwise, it will be added to the back of the queue and it will wait its turn. For the play() function, we will add the properties of the trackResponse to the ServerQueue properties (URL, name, author, etc), and send a play JSON to lavalink with the base64 track string that we sourced earlier. This will successfully send the audio through Discord's voice servers.
Finally, we have to configure the queue handler. For simplicity, we can call this the MusicHandler.
First, we have to assign a ServerQueue to each guild - we will use a dictionary to accomplish this. "public var players = [GuildID : ServerQueue?]()" works. We will create two functions: the first one will create a serverQueue. The function params will be the guild ID, the channel ID, and the player. Each will be passed to the ServerQueue class initializer (along with one extra param: the Node, however we will grab this from the Player parameter). With this serverQueue, we will append it to the dictionary, with the guildID as the key - and then, we will return it.
I mentioned a second function: the destroyPlayer function. This will, as you can probably tell, destroy the queue and remove it from the dictionary. For this, we will just use the guildID as a parameter. First, we have to clear the queue if one is currently playing. Then we can remove the value (ServerQueue), using the key (GuildID). Finally, we will return it.
With that, we have successfully created the ServerQueue. We can now add a .music property to the bot's class, and the MusicHandler() class as the value. In the join command, we will create a lavalink player from the node, connect that player, and then pass it to the MusicHandler so a ServerQueue can be created. Afterwards, we can access it using "SwiftBot.music.players[message.channel?.guild?.id]."
I plan on adding other features to SwiftyBot, including sound effects, databases, and more. I am far from done... I have more tricks up my sleeve.
Each participant is liable for the proper positioning of their wager on the layout no matter whether or not the guess is placed by the dealer. A a perpetual motion machine is a machine that continues to function without drawing vitality from 빅카지노 an exterior supply. The laws of physics say it's inconceivable, but being an inventor, Pascal was making an attempt to defy the odds.
ReplyDelete