Install Asset
Install via Godot
To maintain one source of truth, Godot Asset Library is just a mirror of the old asset library so you can download directly on Godot via the integrated asset library browser
Quick Information
Make requests with the Gemini protocol (https://geminiprotocol.net/) & easily parse gemtext
Godot 4 Gemini Protocl
Godot 4 addon for making requests over Gemini Protocol & parsing Gemtext documents
(not yet) Available on the asset library
Note that for godot reasons, client certificates cannot be used currently. This means that any page that has an AUTH wall cannot be accessed even if you have the correct certificate on hand.
Requesting Gemini documents
GeminiRequest
node
Used to fetch documents from gemini servers
Properties:
port:int
: Port to connect to the serverwith (defaults to1965
)follow_redirects:bool
: Automatically follow redirects without exiting (defaults totrue
)max_redirects:int
: Maximum amount of redirects to follow before aborting (defaults to8
)timeout:float
: Maximum amount of time a request may take. Set to 0 for infinite time (defaults to0
)
Functions:
request(url:String, server_certificate:X509Certificate=null)
Request a page at the URL URL should be a url prefixed withgemini://
server_cetificate is used to verify self-signed certificates. Gemini protocl recommends using certificate pinning. Set to null to disable certificate verification (!!!) Awaiting this is the same as awaiting therequest_completed
signalcancel_request()
: Cancel current ongoing requests- static
make_input_url(base_url:String, input:String)->String
: Add an input query to a normal URL for when the server returns a INPUT response
Signals:
request_completed(result:GeminiRequestResult)
: Main signal to retreive the request result
Enums:
RESPONSECODES
10
:INPUT
:- The basic input status code. A client MUST prompt a user for input, it should be URI-encoded per [STD66] and sent as a query to the same URI that generated this response.
11
:INPUT_SENSITIVE
:- As per status code 10, but for use with sensitive input such as passwords. Clients should present the prompt as per status code 10, but the user's input should not be echoed to the screen to prevent it being read by "shoulder surfers".
20
:SUCCESS
:- The server has successfully parsed and understood the request, and will serve up content of the given MIME type.
30
:REDIRECT_TEMPORARY
:- The basic redirection code. The redirection is temporary and the client should continue to request the content with the original URI.
31
:REDIRECT_PERMANENT
:- The location of the content has moved permanently to a new location, and clients SHOULD use the new location to retrieve the given content from then on.
40
:TEMPFAIL
:- An unspecified condition exists on the server that is preventing the content from being served, but a client can try again to obtain the content.
41
:TEMPFAIL_SERVER_UNAVAILABLE
:- The server is unavailable due to overload or maintenance. (cf HTTP 503)
42
:TEMPFAIL_CGI_ERROR
:- A CGI process, or similar system for generating dynamic content, died unexpectedly or timed out.
43
:TEMPFAIL_PROXY_ERROR
:- A proxy request failed because the server was unable to successfully complete a transaction with the remote host. (cf HTTP 502, 504)
44
:TEMPFAIL_SLOW_DOWN
:- The server is requesting the client to slow down requests, and SHOULD use an exponential back off, where subsequent delays between requests are doubled until this status no longer returned.
50
:PERMFAIL
:- This is the general permanent failure code.
51
:PERMFAIL_NOT_FOUND
:- The requested resource could not be found (you can't find things at Area 51) and no further information is available. It may exist in the future, it may not. Who knows?
52
:PERMFAIL_GONE
:- The resource requested is no longer available and will not be available again. Search engines and similar tools should remove this resource from their indices. Content aggregators should stop requesting the resource and convey to their human users that the subscribed resource is gone. (cf HTTP 410)
53
:PERMFAIL_PROXY_REQUEST_REFUSED
:- The request was for a resource at a domain not served by the server and the server does not accept proxy requests.
59
:PERMFAIL_BAD_REQUEST
:- The server was unable to parse the client's request, presumably due to a malformed request, or the request violated a contraint.
60
:AUTH
:- The content requires a client certificate. The client MUST provide a certificate in order to access the content and SHOULD NOT repeat the request without one. The scope of a certificate generated in response to this status code should is limited to the host and port from which the status code was received and the path of the URL in the original request plus all paths below it. A server MAY require a different certificate for a different path on the same host and port. A server SHOULD allow the same certificate to be used for any content along the given path.
61
:AUTH_CERTIFICATE_NOT_AUTHORIZED
:- The supplied client certificate is not authorised for accessing the particular requested resource. The problem is not with the certificate itself, which may be authorised for other resources.
62
:AUTH_CERTIFICATE_NOT_VALID
:- The supplied client certificate was not accepted because it is not valid. This indicates a problem with the certificate in and of itself, with no consideration of the particular requested resource. The most likely cause is that the certificate's validity start date is in the future or its expiry date has passed, but this code may also indicate an invalid signature, or a violation of a X509 standard requirements.
99
:NONE
:- This is not a gemini response code, but is used when the request fails before it is properly completed.
GeminiRequestResult
resources
This resource is returned by GeminiRequest
node's request_completed
signal.
It has all the returned data by the server, if an error occured, etc etc.
Properties:
url:String
: The URL requestedresult:Error
: The godot error. If this is not OK, then something went wrong with the connectionresponse_code:GeminiRequest.RESPONSECODES
: Responsecode returned by the server. If this is anything other thanGeminiRequest.RESPONSECODES.SUCCESS
(20) you probably need to act.message:String
: Some message received from the server- if
response_code
isSUCCESS
, this will be "" - if
response_code
isINPUT
orINPUT_SENSITIVE
, this will be the input prompt - if
response_code
isREDIRECT_TEMPORARY
orREDIRECT_PERMANENT
, this will be the new URL - if
response_code
is any of the error codes, this will be a user-presentable explanation of the error - if
response_code
isNONE
, this will be either the error_string() ofresult
or another custom GeminiRequest user-presentable error message
- if
mime_type:String
: Mime type of the returned document. For Gemtext documents this istext/gemini
. For anynthing else like images this will be the officia mine type (IEimage/png
)- This is only set if
response_code
isSUCCESS
(20). Otherwise it is null
- This is only set if
body:PackedByteArray
: Binary document content. If this is a gemtext page, you can extract the text by doingbody.get_string_from_utf8()
- This is only set if
response_code
isSUCCESS
(20). Otherwise it is null
- This is only set if
Parsing Gemtext
This addon also contains code to parse gemtext into a more usable format. You should read this if you are unfamiliar with gemtext.
GemtextParser
class
Class used to turn a gemtext document string into a list of GemtextItem
s
Properties:
source:String
: Gemtext source code string (set with constructor)items:Array[GemtextItem]
: (Partial) result of the parsingcollapse_text:bool
: Collapse multiple lines of subsequent text into a singleGemtextItem
(defaults tofalse
)collapse_quotes:bool
: Collapse multiple lines of subsequent quotes into a singleGemtextItem
(defaults tofalse
)collapse_list:bool
: Collapse multiple lines of subsequent lists into a singleGemtextItem
(defaults tofalse
)
Functions:
_init(gemtext:String)
: constructorcomplete()->Array[GemtextItem]
: Run until parsing finishesis_done()->bool
: Returns true if parsing has finishedget_progress()->float
: Returns a % (0.0→1.0) number of how far along the parser isnext()->void
: Parses next line of gemtext. Keep calling this untilis_done()
istrue
- static
parse_all()->Array[GemtextItem]
: Parse a string into an array ofGemtextItem
s without having to instantiate an object
Signals:
done(items:Array[GemtextItem])
: Parser has finished parsingitem_ready(item:GemtextItem)
: A GemtextItem has been completed. Useful for if you are timesharing withnext()
and want to build your scene as it's parsing.
GemtextItem
resource
Resource used for all gemtext items. This is comparable to a javascript DOM element or a godot node (though not in a tree).
Properties:
type:String
: Type of item. Available types are:"text"
,"block"
,"sub-sub-header"
,"sub-header"
,"header"
,"quote"
,"list"
,"link"
content:String
: The text to displayextra:String
: Secondary value. This is only used for"link"
and"block"
items- For
"link"
s, this is the URL - For
"block"
s, this is the alt text/programming language
- For
Examples
Fetching a document & printing its contents
func _ready():
var requester = GeminiRequest.new()
add_child(requester)
var result = await requester.request('gemini://geminiprotocol.net')
if result.result == OK and result.response_code == GeminiRequest.RESPONSECODES.SUCCESS:
print(result.body.get_string_from_utf8())
else:
printerr(result.message)
Handling user input requests
var my_input_node:LineEdit = $my_line_input
func _ready():
requester = GeminiRequest.new()
add_child(requester)
var result = await requester.request('gemini://geminiprotocol.net')
if result.result == OK and result.response_code in [GeminiRequest.RESPONSECODES.INPUT, GeminiRequest.RESPONSECODES.INPUT_SENSITIVE]:
# Server requests an input prompt
print("Server asks for ",result.message)
# Make new url which contains the answer
var prompt_url:String = GeminiRequest.make_input_url( result.url, my_input_node.text )
result = await requester.request( prompt_url )
if result.result == OK and result.response_code == GeminiRequest.RESPONSECODES.SUCCESS:
print(result.body.get_string_from_utf8())
else:
printerr(result.message)
Rendering gemtext
Basic example for rendering gemtext into godot nodes. You can go much fancier than this of course, this is purely functional.
extends VBoxContainer
@export var gemtext:String = ""
var parser:GemtextParser
func _ready():
parser = GemtextParser.new(gemtext)
parser.item_ready.connect(_on_parser_item)
func _process(delta):
if parser.is_done():
set_process(false)
# You probably want to do more than one `next()` per frame
parser.next()
func _on_parser_item(item:GemtextItem):
var out:Control
match item.type:
"block":
out = CodeEdit.new()
out.text = item.content
"link":
out = LinkButton.new()
out.url = item.extra
out.text = item.content
_:
out = Label.new()
out.text = item.content
add_chil(out)
Make requests with the Gemini protocol (https://geminiprotocol.net/) & easily parse gemtext
Reviews
Quick Information
Make requests with the Gemini protocol (https://geminiprotocol.net/) & easily parse gemtext