Using the Github REST API with Godot?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By MOSN

This is a bit of an advanced question, but can anybody tell me whether it would be possible to use the functionality of the Github REST API from a Godot project, specifically the ability to create files within an existing repository: Repositories - GitHub Docs
Could maybe be related HTTPRequest nodes??

So I’ve gotten to the point where I can send data and get a vague unhelpful error from Github. I suspect the problem might be that Github expects the data in JSON format, but HTTPRequest is incapable of sending anything other than Strings (or potentially PoolByteArrays using request_raw() it seems).
Is there any work around for this?

MOSN | 2022-01-09 16:08

:bust_in_silhouette: Reply From: Hugo4IT

(in response to your comment): JSON is just a regular string formatted like a dictionary, thats not the problem, the problem might be any of these things:

  • Content must be encoded in Base64 (you’ll have to make an encoding algorithm yourself, it’s not very hard)
  • You need to have a Personal Access token (from here)
  • Authentication must be provided through the headers, not JSON
  • To change an existing file you’ll need to have an sha1 token of that file (tbh, i dont know what that means but godot strings have a function with sha1 in the name which looks promising)

Based on my research I came up with this:

extends Node2D

var http: HTTPRequest

# Generate an OAuth token at https://github.com/settings/tokens
const OAUTH_TOKEN: String = "ghp_imnotsharingthatlol"

func _ready() -> void:
	http = HTTPRequest.new()
	add_child(http)
	
	http.connect("request_completed", self, "response")
	
	create_or_update_file(
		"Hugo4IT",
		"PreonEngine",
		"test-file.txt",
		"Godot QA test",
		"SGVsbG8sIFdvcmxkIQ==" # I used https://base64.guru/converter/encode to convert "Hello, World!" into Base64
	)

func response(
	result: int,
	response_code: int,
	headers: PoolStringArray,
	body: PoolByteArray
) -> void:
	if response_code >= 200 and response_code < 300:
		print("OK: ", parse_json(body.get_string_from_utf8()))
	else:
		print("ERROR (", response_code, "): ", result)

func create_or_update_file(
	repo_owner: String, # https://github.com/<this>/RepoName
	repo_name: String, # https://github.com/UserName/<this>
	target_file: String, # What file to edit
	commit_message: String, # Commit message
	new_file_content: String, # What to put in the file (MUST BE ENCODED IN BASE64)
	new_file_content_sha: String = "", # Only required when editing an existing file
	commit_on_branch: String = "master" # Very new repos might use "main" as the default branch
) -> void:
	# PUT /repos/{owner}/{repo}/contents/{path}
	var request_url := "https://api.github.com/repos/{owner}/{repo}/contents/{path}".format(
			{"owner": repo_owner, "repo": repo_name, "path": target_file})
	var headers = PoolStringArray([
		"Accept: application/vnd.github.v3+json",
		"Authorization: token " + OAUTH_TOKEN
	])
	var request_data := {
		"message": commit_message,
		"content": new_file_content,
		"sha": new_file_content_sha,
		"branch": commit_on_branch
	}
	http.request(request_url, headers, true, HTTPClient.METHOD_PUT, to_json(request_data))

Which worked like a charm:

Thanks a lot finally got it working!
I had done some of this, but I was missing a few key things.
Also found out my acess token didn’t have the right scopes… Makes sense retrospectively that it needed “repo”, but it wasn’t decribed very well on the page for generating.

MOSN | 2022-01-09 18:09

How would you generate the JWT instead of using a personal access token or a OAuth app?