2023-12-15 22:44:25 +01:00
extends Node
class_name Twitch_Connection
## Handler for a Connection to a Single Twitch Account
##
## Used for getting authentication and handling websocket connections.
signal token_received ( token : String )
@ export var client_id : = " qyjg1mtby1ycs5scm1pvctos7yvyc1 "
@ export var redirect_uri : = " http://localhost "
## Port that the redirect_uri will head to on your local system. Defaults to 80 for most cases. Linux tends to prefer 8000 or possibly 1338
@ export var redirect_port : = " 8000 "
var twitch_url : = " https://id.twitch.tv/oauth2/authorize?response_type=token& "
var auth_server : TCPServer
## Websocket used for handling the chat connection.
var chat_socket = preload ( " res://addons/no_twitch/chat_socket.gd " ) . new ( )
var state : = make_state ( )
var token : String
func _ready ( ) :
chat_socket . chat_received . connect ( check_chat_socket )
2023-12-16 12:23:17 +01:00
## Handles the basic Twitch Authentication process to request and then later receive a Token (using [method check_auth_peer]).
## Returns the authentication URL.
func authenticate_with_twitch ( client_id = client_id ) - > String :
2023-12-15 22:44:25 +01:00
auth_server = TCPServer . new ( )
2023-12-16 12:23:17 +01:00
var url : = create_auth_url ( )
2023-12-15 22:44:25 +01:00
auth_server . listen ( int ( redirect_port ) )
2023-12-16 12:23:17 +01:00
return url
2023-12-15 22:44:25 +01:00
## Sets up a single chat connection. Joining 1 room with [param token] as it's "PASS" specifying what account it's on. And the optional [param default_chat] specifying the default room to join. While [param nick] specifies the "nickname" (Not the username on Twitch)
func setup_chat_connection ( default_chat : String = " " , token : String = token , request_twitch_info = true , nick = " terribletwitch " ) :
# Temporary, closes the socket to allow connecting multiple times without needing to reset.
chat_socket . close ( )
# Connects to the Twitch IRC server.
chat_socket . connect_to_chat ( token , request_twitch_info )
await chat_socket . socket_open
if ! default_chat . is_empty ( ) :
2024-01-15 06:35:16 +01:00
Connections . save_credentials ( { " token " : token , " channel " : default_chat } , " twitch " )
2023-12-15 22:44:25 +01:00
chat_socket . join_chat ( default_chat )
## Joins the given [param channel] over IRC. Essentially just sending JOIN #[param channel]
func join_channel ( channel : String ) :
chat_socket . join_chat ( channel )
func send_chat ( msg : String , channel : String = " " ) :
chat_socket . send_chat ( msg , channel )
func _process ( delta ) :
if auth_server and auth_server . is_listening ( ) and auth_server . is_connection_available ( ) :
check_auth_peer ( auth_server . take_connection ( ) )
if chat_socket :
chat_socket . poll_socket ( )
## Utility function for creating a Twitch Authentication URL with the given
## [param scopes], Twitch Client ID ([param id]) and [param redirect_uri].
## [param id] defaults to [member client_id] and both [param redirect] and
## [param redirect_port]
2023-12-16 12:23:17 +01:00
func create_auth_url ( scopes : Array [ String ] = [ " chat:read " , " chat:edit " ] , port : = redirect_port , id : String = client_id , redirect : String = redirect_uri ) - > String :
2023-12-15 22:44:25 +01:00
var str_scopes : String
for all in scopes :
str_scopes += " " + all
str_scopes = str_scopes . strip_edges ( )
var full_redirect_uri : = redirect
if ! port . is_empty ( ) :
full_redirect_uri += " : " + port
2023-12-16 12:23:17 +01:00
var url : = twitch_url + " client_id= " + id + " &redirect_uri= " + full_redirect_uri + " &scope= " + str_scopes + " &state= " + str ( state )
2023-12-15 22:44:25 +01:00
return url
## Utility function for creating a "state" used for different requests for some extra security as a semi password.
func make_state ( len : int = 16 ) - > String :
var crypto = Crypto . new ( )
var state = crypto . generate_random_bytes ( len ) . hex_encode ( )
return state
func check_auth_peer ( peer : StreamPeerTCP ) :
var info = peer . get_utf8_string ( peer . get_available_bytes ( ) )
printraw ( info )
var root : = redirect_uri
if ! redirect_port . is_empty ( ) :
root += " : " + redirect_port
root += " / "
var script = " <script>fetch( ' " + root + " ' + window.location.hash.substr(1))</script> "
peer . put_data ( str ( " HTTP/1.1 200 \n \n " + script ) . to_utf8_buffer ( ) )
var resp_state = info . split ( " &state= " )
# Ensures that the received state is correct.
if ! resp_state . size ( ) > 1 or resp_state [ 0 ] == state :
return
var token = info . split ( " access_token= " ) [ 1 ] . split ( " &scope= " ) [ 0 ] . strip_edges ( )
printraw ( " Token: " , token , " \n " )
self . token = token
token_received . emit ( token )
func check_chat_socket ( dict ) :
prints ( dict . user , dict . message )