Skip to content

Commit

Permalink
First Alpha!
Browse files Browse the repository at this point in the history
  • Loading branch information
elenakrittik committed Jul 1, 2022
0 parents commit 01d8f4a
Show file tree
Hide file tree
Showing 136 changed files with 5,309 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.cpp text
*.c text
*.h text
*.gd text
*.cs text

# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf

# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Import cache
.import/

# Binaries
bin/
build/
lib/
213 changes: 213 additions & 0 deletions addons/godot_editor_discord_presence/Discord RPC/DiscordRPC.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
class_name DiscordRPC extends Node

signal rpc_ready(user)
signal authorized(code)
signal authenticated(expires)
signal guild_status(id, name, icon_url)
signal guild_create(id, name)
signal channel_create(id, name, type)
signal voice_channel_select(channel_id, guild_id)
signal voice_state_create(voice_state)
signal voice_state_update(voice_state)
signal voice_state_delete(voice_state)
signal voice_settings_update(voice_settings)
signal voice_connection_status(state, hostname, pings, average_ping, last_ping)
signal speaking_start(channel_id, user_id)
signal speaking_stop()
signal message_create(message)
signal message_update(message)
signal message_delete(message)
signal notification_create(channel_id, message, icon_url, title, body)
signal activity_join(secret)
signal activity_spectate(secret)
signal activity_join_request(user)
signal raw_data(data)
signal rpc_closed()
signal rpc_error(code)


enum {
DISCONNECTED,
CONNECTING,
CONNECTED,
DISCONNECTING
}

enum {
ERR_UNSUPPORTED = 49,
ERR_HANDSHAKE,
ERR_CLIENT_NOT_FOUND
}

const VERSION: int = 1
const DISCORD_API_ENDPOINT: String = "https://discord.com/api/%s"

var _ipc: IPC setget __set
var _modules: Dictionary setget __set

var status: int = DISCONNECTED setget __set
var client_id: int setget __set
var scopes: PoolStringArray setget __set

func _init() -> void:
_ipc = IPC.new()
self._ipc.connect("data_recieved", self, "_on_data")
self.install_module(RichPresenceModule.new())
self.set_process(false)

func establish_connection(_client_id: int) -> void:
if (self.is_connected_to_client()):
push_error("This DiscordRPC instance is already in an active connection")
return

if (not self.is_supported()):
push_error("IPC error: Unsuported platform")
emit_signal("rpc_error", ERR_UNSUPPORTED)
return

if (not self.is_inside_tree()):
push_error("DiscordRPC isn't inside a scene tree")
emit_signal("rpc_error", ERR_UNCONFIGURED)
return

client_id = _client_id
status = CONNECTING
for i in range(10):
var path = IPC.get_pipe_path(i)
if (self._ipc.open(path) == OK):
self._ipc.setup()
self._handshake()
self.set_process(true)
return
self._ipc.close()
self.emit_signal("rpc_error", ERR_CLIENT_NOT_FOUND)
self.shutdown()

func is_connected_to_client() -> bool:
return self._ipc and self._ipc.is_open() and self.status != DISCONNECTED

func authorize(_scopes: PoolStringArray, secret: String) -> void:
var request: IPCPayload = IPCUtil.AuthorizePayload.new(self.client_id, _scopes)
var response: IPCPayload = yield(self._ipc.send(request), "completed")
if (not response.is_error()):
var code: String = response.data["code"]
var auth_token: String = yield(self.get_auth_token(code, secret), "completed")
if (not auth_token.empty()):
self.emit_signal("authorized", auth_token)
self.authenticate(auth_token)

func authenticate(access_token: String) -> void:
var request: IPCPayload = IPCUtil.AuthenticatePayload.new(access_token)
var response: IPCPayload = yield(self._ipc.send(request), "completed")
if (not response.is_error()):
scopes = response.data["scopes"]
self.emit_signal("authenticated", response.data["expires"])

func get_auth_token(authorize_code: String, secret: String, redirect_url: String = "http://127.0.0.1") -> String:
var http_request: HTTPRequest = HTTPRequest.new()
http_request.use_threads = OS.can_use_threads()
var url: String = DISCORD_API_ENDPOINT % "oauth2/token"
var headers: PoolStringArray = ["Content-Type: application/x-www-form-urlencoded"]
var data: Dictionary = {
"client_id": self.client_id,
"client_secret": secret,
"grant_type": "authorization_code",
"code": authorize_code,
"redirect_uri": redirect_url
}

self.add_child(http_request)
http_request.request(
url,
headers,
true,
HTTPClient.METHOD_POST,
URLUtil.dict_to_url_encoded(data)
)
var response: Array = yield(http_request, "request_completed")
var result: int = response[0]
var code: int = response[1]
var body: PoolByteArray = response[3]

http_request.queue_free()

return parse_json(body.get_string_from_utf8()).get("access_token", "")

func subscribe(event: String, arguments: Dictionary = {}) -> void:
self._ipc.send(IPCUtil.SubscribePayload.new(event, arguments))

func unsubscribe(event: String, arguments: Dictionary = {}) -> void:
self._ipc.send(IPCUtil.UnsubscribePayload.new(event, arguments))

func shutdown() -> void:
status = DISCONNECTING
self._ipc.close()
status = DISCONNECTED
self.set_process(false)
self.emit_signal("rpc_closed")

func install_module(module: IPCModule) -> void:
if (not self._modules.has(module.name)):
module.initilize(self._ipc)
self._modules[module.name] = module

func get_module(name: String) -> IPCModule:
return self._modules.get(name)

func uninstall_module(name: String) -> void:
# warning-ignore:return_value_discarded
self._modules.erase(name)

func ipc_call(function: String, arguments: Array = []):
for module in self._modules.values():
if (function in module.get_functions()):
return module.callv(function, arguments)
push_error("Calling non-existant function \"%s\" via ipc_call" % function)
return null

func _handshake() -> void:
if (self.status == CONNECTED):
push_error("Already handshaked !")
return
var request: IPCPayload = IPCUtil.HandshakePayload.new(VERSION, self.client_id)
var response: IPCPayload = yield(self._ipc.send(request), "completed")
if (response.op_code != IPCPayload.OpCodes.CLOSE and not response.is_error()):
status = CONNECTED
self.emit_signal("rpc_ready", response.data["user"])
return
self.emit_signal("rpc_error", ERR_HANDSHAKE)
self.shutdown()

func _notification(what: int) -> void:
match what:
NOTIFICATION_PREDELETE:
self.shutdown()

func _process(_delta: float) -> void:
self._ipc.poll()
_ipc.poll()
if not _ipc.is_open():
shutdown()

func _on_data(payload: IPCPayload) -> void:
if (payload.is_error()):
push_error("IPC: Recieved error code: %d: %s" % [payload.get_error_code(), payload.get_error_messsage()])

if (payload.op_code == IPCPayload.OpCodes.CLOSE):
self.shutdown()
return

self.emit_signal("raw_data", payload)

var signal_name = payload.event.to_lower()
if (payload.command == "DISPATCH" and self.has_signal(signal_name)):
self.callv("emit_signal", [signal_name] + payload.data.values())

func _to_string() -> String:
return "[DiscordRPC:%d]" % self.get_instance_id()

func __set(_value) -> void:
pass

static func is_supported() -> bool:
return not IPC.get_pipe() == null
46 changes: 46 additions & 0 deletions addons/godot_editor_discord_presence/Discord RPC/DiscordRPCEnum.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class_name DiscordRPCEnum

const Commands: Dictionary = {
DISPATCH = "DISPATCH",
AUTHORIZE = "AUTHORIZE",
AUTHENTICATE = "AUTHENTICATE",
GET_GUILD = "GET_GUILD",
GET_GUILDS = "GET_GUILDS",
GET_CHANNEL = "GET_CHANNEL",
GET_CHANNELS = "GET_CHANNELS",
SUBSCRIBE = "SUBSCRIBE",
UNSUBSCRIBE = "UNSUBSCRIBE",
SET_USER_VOICE_SETTINGS = "SET_USER_VOICE_SETTINGS",
SELECT_VOICE_CHANNEL = "SELECT_VOICE_CHANNEL",
GET_SELECTED_VOICE_CHANNEL = "GET_SELECTED_VOICE_CHANNEL",
SELECT_TEXT_CHANNEL = "SELECT_TEXT_CHANNEL",
GET_VOICE_SETTINGS = "GET_VOICE_SETTINGS",
SET_VOICE_SETTINGS = "SET_VOICE_SETTINGS",
CAPTURE_SHORTCUT = "CAPTURE_SHORTCUT",
SET_CERTIFIED_DEVICES = "SET_CERTIFIED_DEVICES",
SET_ACTIVITY = "SET_ACTIVITY",
SEND_ACTIVITY_JOIN_INVITE = "SEND_ACTIVITY_JOIN_INVITE",
CLOSE_ACTIVITY_REQUEST = "CLOSE_ACTIVITY_REQUEST"
}

const Events: Dictionary = {
READY = "READY",
ERROR = "ERROR",
GUILD_STATUS = "GUILD_STATUS",
GUILD_CREATE = "GUILD_CREATE",
CHANNEL_CREATE = "CHANNEL_CREATE",
VOICE_CHANNEL_SELECT = "VOICE_CHANNEL_SELECT",
VOICE_STATE_CREATE = "VOICE_STATE_CREATE",
VOICE_STATE_UPDATE = "VOICE_STATE_UPDATE",
VOICE_STATE_DELETE = "VOICE_STATE_DELETE",
VOICE_SETTINGS_UPDATE = "VOICE_SETTINGS_UPDATE",
VOICE_CONNECTION_STATUS = "VOICE_CONNECTION_STATUS",
SPEAKING_START = "SPEAKING_START",
SPEAKING_STOP = "SPEAKING_STOP",
MESSAGE_CREATE = "MESSAGE_CREATE",
MESSAGE_DELETE = "MESSAGE_DELETE",
NOTIFICATION_CREATE = "NOTIFICATION_CREATE",
ACTIVITY_JOIN = "ACTIVITY_JOIN",
ACTIVITY_SPECTATE = "ACTIVITY_SPECTATE",
ACTIVITY_JOIN_REQUEST = "ACTIVITY_JOIN_REQUEST"
}
Loading

0 comments on commit 01d8f4a

Please sign in to comment.