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) ## 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: auth_server = TCPServer.new() var url := create_auth_url() auth_server.listen(int(redirect_port)) return url ## 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(): Connections.save_credentials({"token" : token, "channel" : default_chat}, "twitch") 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] func create_auth_url(scopes : Array[String] = ["chat:read", "chat:edit"], port := redirect_port, id : String = client_id, redirect : String = redirect_uri) -> String: 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 var url := twitch_url + "client_id=" + id + "&redirect_uri=" + full_redirect_uri + "&scope=" + str_scopes + "&state=" + str(state) 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 = "" 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)