Wrapper around Discord Social SDK.
What is Discord Social SDK? You can read about it from Discord it self:
- https://discord.com/developers/docs/discord-social-sdk/overview
- https://discord.com/developers/docs/social-sdk/index.html
- https://support-dev.discord.com/hc/en-us/articles/30127085446039-Introducing-the-Discord-Social-SDK
But if you want me to be brief... It's basically a way to use Discord infrastructure in your game. For example, instead of developing a text/voice chat for your game, you could just request Discord to create one for you.
What is this GDExtension? It's a wrapper around the SDK, so you can interact with the SDK through GDScript (instead of C++).
What can you do with the SDK?
- Send/Accept friend request.
- Block/Unblock users.
- Send/Read direct messages.
- Interact with Rich Presence.
- Setup your Activity.
- Invite to game.
- Create lobbies.
- Use lobby text chat.
- Use lobby voice chat.
- I don't know all functionalities but is suppose to do everything that the SDK can do...
These are the currently supported platforms:
- Linux (x86_64)
- Windows
If you want to request support for other build, make an issue.
If you already knows how to make other build, feel free to make a pull request.
Important
Before starting, you need to follow 3 steps from Getting Started with C++:
- Step 1: Create a Discord Developer Team.
- Step 2: Create a Discord Application.
- Step 3: Enable Discord Social SDK for Your App.
It's available in Godot Asset Library, so you can search and install through Godot.
- Go to releases from Github.
- Download latest release ZIP.
- Extract
addonsdirectory from ZIP.- It will be inside a
demodirectory.
- It will be inside a
- Move
addonsdirectory to your project directory.- If your project already have an
addonsdirectory, copyaddons/discord_social_sdkto your projectaddons.
- If your project already have an
This GDExtension is a wrapper around the C++ SDK, which means that each GDScript method it's just calling the C++ counterpart.
If you understand C++, you could easily read a C++ code and convert it to GDScript. For example, I was able to convert their conclusion code from Getting Started with C++ to:
extends Control
# Replace with your Discord Application ID
var APPLICATION_ID: int = 1349146942634065960
var client := DiscordClient.new()
var args := DiscordAuthorizationArgs.new()
var code_verifier: DiscordAuthorizationCodeVerifier = null
func _ready() -> void:
print("๐ Initializing Discord SDK...")
client.add_log_callback(_on_log_message, DiscordLoggingSeverity.INFO)
client.set_status_changed_callback(_on_status_changed)
code_verifier = client.create_authorization_code_verifier()
args.set_client_id(APPLICATION_ID)
args.set_scopes(DiscordClient.get_default_presence_scopes())
args.set_code_challenge(code_verifier.challenge())
client.authorize(args, _on_authorized)
func _process(_delta: float) -> void:
Discord.run_callbacks()
func _on_log_message(message: String, severity: DiscordLoggingSeverity.Enum) -> void:
print("[%s] %s" % [Discord.enum_to_string(severity, DiscordLoggingSeverity.id), message])
func _on_status_changed(status: DiscordClientStatus.Enum, error: DiscordClientError.Enum, error_detail: int) -> void:
print("๐ Status changed: %s" % status)
if status == DiscordClientStatus.READY:
print("โ
Client is ready! You can now call SDK functions.")
print("๐ฅ Friends Count: %s" % client.get_relationships().size())
var activity := DiscordActivity.new()
activity.set_type(DiscordActivityTypes.PLAYING)
activity.set_state("In Competitive Match")
activity.set_details("Rank: Diamond II")
client.update_rich_presence(activity, _on_rich_presence_updated)
elif error != DiscordClientError.NONE:
print("โ Connection Error: %s - Details: %s" % [error, error_detail])
func _on_rich_presence_updated(result: DiscordClientResult) -> void:
if result.successful():
print("๐ฎ Rich Presence updated successfully!")
else:
print("โ Rich Presence update failed")
func _on_authorized(result: DiscordClientResult, code: String, redirect_uri: String) -> void:
if not result.successful():
print("โ Authentication Error: %s" % result.error())
else:
print("โ
Authorization successful! Getting access token...")
client.get_token(APPLICATION_ID, code, code_verifier.verifier(), redirect_uri, _on_token_received)
func _on_token_received(
_result: DiscordClientResult,
access_token: String,
_refresh_token: String,
_token_type: DiscordAuthorizationTokenType.Enum,
_expires_in: int,
_scopes: String
) -> void:
print("๐ Access token received! Establishing connection...")
client.update_token(DiscordAuthorizationTokenType.BEARER, access_token, _on_token_updated)
func _on_token_updated(result: DiscordClientResult) -> void:
if result.successful():
print("๐ Token updated, connecting to Discord...")
client.connect_discord()Check these two directories:
- Discord examples.
- Examples made using Discord Social SDK Documentation as base.
- In my opnion, you should read the official documentation to understand how the SDK works and only look at these examples when curious about the GDScript version.
- GDExtension examples.
- Examples made by me (for fun).
You probably already noticed, but their SDK makes heavy use of callbacks and we just replicate their behaviour in this GDExtension.
At first you may think that your Application ID is public information... But it's not!
Reading Discord Developer Terms of Service section 2.d, you will find:
You will use any developer credentials (such as your Application ID, passwords, keys, tokens, and client secrets) we assign to you solely with your Application and the applicable APIs (and will not permit or enable any other Application to use them) and will treat them as Discord confidential information...
This security is needed because Application ID is all that somebody needs to interact with the Discord Social SDK. In other words, knowing your Application ID is everything that a person needs to impersonate your application.
That's why Discord Social SDK should only be used on games that already have servers (unless someone prove me wrong).
If your game runs everything locally, that means that you need to put your Application ID in the binary and this means that someone can reverse engineer to get it (even if you compile with PCK encryption key).
- As I said before, the GDExtension is just a wrapper around C++ SDK. In other words, you can probably make all the same things that the C++ SDK can do (I hope).
- I belive that they use Doxygen to generate their documentation, which I'm also using in this project. So we probably have the same level of documentation (it may need a little formatting, but we have it!).
- As counterpart of C++
std::optional<T>type, I'm usingVariant(which will holdnullor an actual value).- This could change in the future if I decide to create my own class "Optional".
- Some functions were renamed because their name was already being used in Godot class. I just added a
_discordto their name.- For example, the class
DiscordClientwill have these two methods:connect(): Godot methodObject.connect().connect_discord(): Discord methoddiscordpp:Client::Connect().
- For example, the class
- There is no
uintin GDScript, so you always receive anintfrom the GDExtension.- If you don't intend to change the data, everything will be fine because there is no data lost when converting between
uintandint. - If you do intend to change the data, you should know which operations can corrupt your data.
- Reference: godotengine/godot-proposals#9740 (comment)
- If you don't intend to change the data, everything will be fine because there is no data lost when converting between
- No signals usage.
- I would love to transform some of these callbacks into signals (
โฅ๏ธ ), but is not possible to identify when can it be done just by looking at functions signature. For example:void xxxxx(Callback cb);- If the function name is
set_xxx_callback: You know that will call you function when "xxx" happens. - If the function name is
do_xxx: You know that will do "xxx" and call you function when it's done. - The first case can be transformed to signal, while the second can't... Analysing the function name and deciding what to do is a bit too much for me.
- If the function name is
- I would love to transform some of these callbacks into signals (
- Each enum has it own class.
- The enum
discordpp::HttpStatusCodewill beDiscordHttpStatusCode. - The enum
discordpp::Client::Statuswill beDiscordClientStatus. - This happens because the Godot C++ doesn't let me use the same name in different enums.
- Just to be clear, you can do it through GDScript but not through Godot C++.
- Any enum created through Godot C++ will also be added as constant for the class.
- Creating
Discord.HttpStatusCode.NONEwill also createDiscord.NONE, which would cause conflict when creatingDiscord.ExternalIdentityProviderType.NONEbecause would attempt to create anotherDiscord.NONE.
- Creating
- Reference: godotengine/godot-cpp#1910
- The enum
- There is no function overloading in GDScript, so I had to change the function signature in any way possible to tell me which function to call.
- These functions will have an extra performance cost.
- In compiled languages, the identification of the right function can be done during compilation.
- In interpreted languages that use Duck typing (like GDScript), this happens during execution.
- For example:
discordpp::EnumToString()receives 1 argument which can be many enums:discordpp::ActivityActionTypes valuediscordpp::ActivityGamePlatforms valuediscordpp::ActivityPartyPrivacy valuediscordpp::ActivityTypes value- ...
Discord.enum_to_string()receives 2 arguements, where the second identify which enum:value: int, enum_id: int- Where
enum_idis a constant in the enum class:DiscordActivityActionTypes.idDiscordActivityGamePlatforms.idDiscordActivityPartyPrivacy.idDiscordActivityTypes.id
- Where
- These functions will have an extra performance cost.
For development details go to DEVELOPMENT.md.
