Initial Android commit
This commit is contained in:
commit
1e2b80c13d
8521 changed files with 231475 additions and 0 deletions
8
addons/godot-twicil/godot-twicil-init.gd
Normal file
8
addons/godot-twicil/godot-twicil-init.gd
Normal file
|
@ -0,0 +1,8 @@
|
|||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
add_custom_type("TwiCIL", "Node", load("res://addons/godot-twicil/godot_twicil.gd"), load("res://addons/godot-twicil/sprites/twicil-icon.png"))
|
||||
|
||||
func _exit_tree():
|
||||
remove_custom_type("TwiCIL")
|
9
addons/godot-twicil/godot-twicil.tscn
Normal file
9
addons/godot-twicil/godot-twicil.tscn
Normal file
|
@ -0,0 +1,9 @@
|
|||
[gd_scene load_steps=1 format=2]
|
||||
|
||||
[ext_resource path="res://godot_twicil.gd" type="Script" id=1]
|
||||
|
||||
[node name="GodotTwiCIL" type="Node"]
|
||||
|
||||
script = ExtResource( 1 )
|
||||
CONNECT_WAIT_TIMEOUT = 1
|
||||
COMMAND_WAIT_TIMEOUT = 0.3
|
257
addons/godot-twicil/godot_twicil.gd
Normal file
257
addons/godot-twicil/godot_twicil.gd
Normal file
|
@ -0,0 +1,257 @@
|
|||
extends IrcClientSecure
|
||||
class_name TwiCIL
|
||||
|
||||
signal raw_response_recieved(response)
|
||||
signal user_appeared(user)
|
||||
signal user_disappeared(user)
|
||||
signal message_recieved(sender, text, emotes)
|
||||
|
||||
signal emote_recieved(user, emote_reference)
|
||||
|
||||
signal texture_recieved(texture)
|
||||
|
||||
enum IRCCommands{PING, PONG, PRIVMSG, JOIN, PART, NAMES}
|
||||
|
||||
const TWITCH_IRC_CHAT_HOST = "wss://irc-ws.chat.twitch.tv"
|
||||
const TWITCH_IRC_CHAT_PORT = 443
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
onready var tools = HelperTools.new()
|
||||
onready var commands = InteractiveCommands.new()
|
||||
onready var chat_list = ChatList.new()
|
||||
|
||||
|
||||
var twitch_emotes_cache:TwitchEmotesCache
|
||||
var bttv_emotes_cache:BttvEmotesCache
|
||||
var ffz_emotes_cache:FfzEmotesCache
|
||||
|
||||
var twitch_api_wrapper:TwitchApiWrapper
|
||||
|
||||
var irc_commands = {
|
||||
IRCCommands.PING:"PING",
|
||||
IRCCommands.PONG:"PONG",
|
||||
IRCCommands.PRIVMSG:"PRIVMSG",
|
||||
IRCCommands.JOIN:"JOIN",
|
||||
IRCCommands.PART:"PART",
|
||||
IRCCommands.NAMES:"/NAMES"
|
||||
}
|
||||
|
||||
var curr_channel = ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var user_emotes_queue: = Dictionary()
|
||||
|
||||
|
||||
|
||||
func connect_to_twitch_chat()->bool:
|
||||
return .connect_to_host(TWITCH_IRC_CHAT_HOST, TWITCH_IRC_CHAT_PORT)
|
||||
|
||||
func connect_to_channel(channel, client_id, password, nickname, realname = ""):
|
||||
_connect_to(
|
||||
channel,
|
||||
nickname,
|
||||
nickname if realname == "" else realname,
|
||||
password,
|
||||
client_id
|
||||
)
|
||||
|
||||
|
||||
|
||||
twitch_api_wrapper.set_credentials(client_id, password)
|
||||
|
||||
func _connect_to(channel, nickname, realname, password, client_id):
|
||||
.send_command("PASS %s" % password)
|
||||
|
||||
.send_command("NICK " + nickname)
|
||||
|
||||
.send_command(str("USER ", client_id, " ", _host, " bla:", realname))
|
||||
.send_command("JOIN #" + channel)
|
||||
|
||||
.send_command("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership")
|
||||
|
||||
curr_channel = channel
|
||||
|
||||
func Disconnect():
|
||||
.Disconnect();
|
||||
|
||||
func IsConnected()->bool:
|
||||
return .IsConnected();
|
||||
|
||||
func send_message(text):
|
||||
.send_command(str("PRIVMSG #", curr_channel, " :", text))
|
||||
|
||||
func send_whisper(recepient, text):
|
||||
send_message(str("/w ", recepient, " ", text))
|
||||
|
||||
func request_twitch_emote(user_name:String, id:int)->void :
|
||||
if not user_emotes_queue.has(id):
|
||||
user_emotes_queue[id] = []
|
||||
|
||||
user_emotes_queue[id].append(user_name)
|
||||
|
||||
twitch_emotes_cache.get_emote(id)
|
||||
|
||||
func request_bttv_emote(user_name:String, code:String)->void :
|
||||
var id:String = bttv_emotes_cache.available_emotes.get(code)
|
||||
|
||||
if not user_emotes_queue.has(id):
|
||||
user_emotes_queue[id] = []
|
||||
|
||||
user_emotes_queue[id].append(user_name)
|
||||
|
||||
bttv_emotes_cache.get_emote(code)
|
||||
|
||||
func request_ffz_emote(user_name:String, code:String)->void :
|
||||
var id:String = ffz_emotes_cache.available_emotes.get(code, {}).get("id", "")
|
||||
|
||||
if not user_emotes_queue.has(id):
|
||||
user_emotes_queue[id] = []
|
||||
|
||||
user_emotes_queue[id].append(user_name)
|
||||
|
||||
ffz_emotes_cache.get_emote(code)
|
||||
|
||||
func request_emote_from(emotes:Array, user_name:String, index:int)->void :
|
||||
if emotes.empty():
|
||||
return
|
||||
|
||||
var emote = emotes[index]
|
||||
|
||||
if emote.get("type") == TwitchMessage.EmoteType.TWITCH:
|
||||
var emote_id: = int(emote.get("id"))
|
||||
request_twitch_emote(user_name, emote_id)
|
||||
|
||||
elif emote.get("type") == TwitchMessage.EmoteType.BTTV:
|
||||
var emote_code: = emote.get("code") as String
|
||||
request_bttv_emote(user_name, emote_code)
|
||||
|
||||
elif emote.get("type") == TwitchMessage.EmoteType.FFZ:
|
||||
var emote_code: = emote.get("code") as String
|
||||
request_ffz_emote(user_name, emote_code)
|
||||
|
||||
|
||||
func __init_emotes_caches()->void :
|
||||
twitch_emotes_cache = TwitchEmotesCache.new()
|
||||
add_child(twitch_emotes_cache)
|
||||
|
||||
bttv_emotes_cache = BttvEmotesCache.new()
|
||||
add_child(bttv_emotes_cache)
|
||||
|
||||
ffz_emotes_cache = FfzEmotesCache.new()
|
||||
add_child(ffz_emotes_cache)
|
||||
|
||||
func __init_twitch_api()->void :
|
||||
twitch_api_wrapper = TwitchApiWrapper.new(http_request_queue, "")
|
||||
|
||||
func __connect_signals():
|
||||
connect("message_recieved", commands, "_on_message_recieved")
|
||||
connect("response_recieved", self, "_on_response_recieved")
|
||||
connect("http_response_recieved", self, "_on_http_response_recieved")
|
||||
|
||||
twitch_emotes_cache.connect("emote_retrieved", self, "_on_emote_retrieved")
|
||||
bttv_emotes_cache.connect("emote_retrieved", self, "_on_emote_retrieved")
|
||||
ffz_emotes_cache.connect("emote_retrieved", self, "_on_emote_retrieved")
|
||||
|
||||
twitch_api_wrapper.connect("api_user_info", self, "_on_twitch_api_api_user_info")
|
||||
|
||||
func __parse(string:String)->TwitchIrcServerMessage:
|
||||
var args = []
|
||||
var twitch_prefix = ""
|
||||
var prefix = ""
|
||||
var trailing = []
|
||||
var command
|
||||
|
||||
if string == null:
|
||||
return TwitchIrcServerMessage.new("", "", "", [])
|
||||
|
||||
if string.substr(0, 1) == "@":
|
||||
var temp = tools.split_string(string.substr(1, string.length() - 1), " ", 1)
|
||||
twitch_prefix = temp[0]
|
||||
string = temp[1]
|
||||
|
||||
if string.substr(0, 1) == ":":
|
||||
var temp = tools.split_string(string.substr(1, string.length() - 1), " ", 1)
|
||||
prefix = temp[0]
|
||||
string = temp[1]
|
||||
|
||||
if string.find(" :") != - 1:
|
||||
var temp = tools.split_string(string, " :", 1)
|
||||
string = temp[0]
|
||||
trailing = temp[1]
|
||||
|
||||
args = tools.split_string(string, [" ", " ", "\n"])
|
||||
|
||||
args.append(trailing)
|
||||
else :
|
||||
args = tools.split_string(string, [" ", " ", "\n"])
|
||||
|
||||
command = args[0]
|
||||
args.pop_front()
|
||||
|
||||
return TwitchIrcServerMessage.new(twitch_prefix, prefix, command, args)
|
||||
|
||||
|
||||
|
||||
func _ready():
|
||||
__init_twitch_api()
|
||||
__init_emotes_caches()
|
||||
__connect_signals()
|
||||
|
||||
|
||||
|
||||
func _on_response_recieved(response):
|
||||
emit_signal("raw_response_recieved", response)
|
||||
|
||||
for single_response in response.split("\n", false):
|
||||
single_response = __parse(single_response.strip_edges(false))
|
||||
|
||||
|
||||
if single_response.command == irc_commands[IRCCommands.PING]:
|
||||
.send_command(str(irc_commands[IRCCommands.PONG], " ", single_response.params[0]))
|
||||
|
||||
|
||||
elif single_response.command == irc_commands[IRCCommands.PRIVMSG]:
|
||||
var twitch_message:TwitchMessage = TwitchMessage.new(
|
||||
single_response,
|
||||
bttv_emotes_cache.available_emotes,
|
||||
ffz_emotes_cache.available_emotes
|
||||
)
|
||||
|
||||
emit_signal(
|
||||
"message_recieved",
|
||||
twitch_message.chat_message.name,
|
||||
twitch_message.chat_message.text,
|
||||
twitch_message.emotes
|
||||
)
|
||||
|
||||
elif single_response.command == irc_commands[IRCCommands.JOIN]:
|
||||
var user_name = MessageWrapper.get_sender_name(single_response)
|
||||
chat_list.add_user(user_name)
|
||||
._log(str(user_name, " has joined chat"))
|
||||
emit_signal("user_appeared", user_name)
|
||||
|
||||
elif single_response.command == irc_commands[IRCCommands.PART]:
|
||||
var user_name = MessageWrapper.get_sender_name(single_response)
|
||||
chat_list.remove_user(user_name)
|
||||
._log(str(user_name, " has left chat"))
|
||||
emit_signal("user_disappeared", user_name)
|
||||
|
||||
func _on_emote_retrieved(emote_reference:Reference)->void :
|
||||
var emote_id:String = emote_reference.id
|
||||
var user:String = (user_emotes_queue.get(emote_id, []) as Array).pop_front()
|
||||
|
||||
emit_signal("emote_recieved", user, emote_reference)
|
||||
|
||||
func _on_twitch_api_api_user_info(data):
|
||||
var user_id: = str(data.get("data", [{}])[0].get("id", 0))
|
||||
|
||||
ffz_emotes_cache.init_emotes(user_id)
|
88
addons/godot-twicil/helpers/api/twitch_api_wrapper.gd
Normal file
88
addons/godot-twicil/helpers/api/twitch_api_wrapper.gd
Normal file
|
@ -0,0 +1,88 @@
|
|||
extends Object
|
||||
class_name TwitchApiWrapper
|
||||
|
||||
|
||||
signal api_response_recieved(rquest_id, response)
|
||||
signal api_response_failed(response_code, http_headers)
|
||||
|
||||
signal api_user_info(data)
|
||||
|
||||
|
||||
const API_REQUEST_USER_INFO = "user_info"
|
||||
|
||||
const API_URLS = {
|
||||
API_REQUEST_USER_INFO:{
|
||||
"template":"https://api.twitch.tv/helix/users?login={{login}}",
|
||||
"params":[
|
||||
"{{login}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
var client_id: = ""
|
||||
var oauth: = ""
|
||||
var http_request_queue:HttpRequestQueue
|
||||
|
||||
|
||||
|
||||
func _init(http_request_queue:HttpRequestQueue, client_id:String)->void :
|
||||
self.client_id = client_id
|
||||
self.http_request_queue = http_request_queue
|
||||
|
||||
__connect_signals()
|
||||
|
||||
|
||||
|
||||
func set_credentials(client_id:String, raw_oauth_string:String)->void :
|
||||
self.client_id = client_id
|
||||
self.oauth = raw_oauth_string.split(":")[1]
|
||||
|
||||
func get_raw_response(request_id:String, url:String):
|
||||
var headers:PoolStringArray = [
|
||||
"Client-ID: " + client_id,
|
||||
"Authentication: Bearer " + oauth
|
||||
]
|
||||
|
||||
http_request_queue.enqueue_request(request_id, url, headers)
|
||||
|
||||
func get_api_url(url_id:String, params:Array)->String:
|
||||
var url:String
|
||||
var url_info:Dictionary = API_URLS.get(url_id, {})
|
||||
var url_template:String = url_info.get("template", "")
|
||||
var url_params:Array = url_info.get("params", [])
|
||||
|
||||
if params.size() < url_params.size():
|
||||
return str("Wrong params count. Expected ", url_params.size(), " but got ", params.size(), " instead.")
|
||||
|
||||
url = url_template
|
||||
|
||||
for i in range(url_params.size()):
|
||||
url = url.replace(url_params[i], params[i])
|
||||
|
||||
return url
|
||||
|
||||
func get_user_info(user_name:String):
|
||||
var url:String = get_api_url(API_REQUEST_USER_INFO, [user_name])
|
||||
get_raw_response(API_REQUEST_USER_INFO, url)
|
||||
|
||||
|
||||
|
||||
func __connect_signals()->void :
|
||||
http_request_queue.connect("request_completed_ex", self, "_on_http_request_queue_request_completed")
|
||||
|
||||
|
||||
|
||||
func _on_http_request_queue_request_completed(id:String, result:int, response_code:int, http_headers:HttpHeaders, body:PoolByteArray)->void :
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
if http_headers.get("Content-Type") == HttpHeaders.HTTP_CONTENT_TYPE_JSON_UTF8:
|
||||
var data = parse_json(body.get_string_from_utf8())
|
||||
|
||||
emit_signal("api_response_recieved", id, data)
|
||||
|
||||
if response_code == 200:
|
||||
match id:
|
||||
API_REQUEST_USER_INFO:
|
||||
emit_signal("api_user_info", data)
|
||||
|
||||
|
||||
emit_signal("api_response_failed", response_code, http_headers)
|
96
addons/godot-twicil/helpers/cache/base_emotes_cache.gd
vendored
Normal file
96
addons/godot-twicil/helpers/cache/base_emotes_cache.gd
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
extends Node
|
||||
class_name BaseEmotesCache
|
||||
|
||||
signal downloaded(content)
|
||||
|
||||
var http_request_queue:HttpRequestQueue
|
||||
var ready_to_deliver_emotes: = false
|
||||
|
||||
class DownloadedContent:
|
||||
const CONTENT_TYPE_IMAGE_PNG = "image/png"
|
||||
const CONTENT_TYPE_IMAGE_JPEG = "image/jpeg"
|
||||
|
||||
|
||||
var id:String
|
||||
var type:String
|
||||
var data:PoolByteArray
|
||||
var image:Image
|
||||
|
||||
|
||||
func _init(id:String, type:String, data:PoolByteArray):
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
func get_image_from_data()->Image:
|
||||
var image:Image = Image.new()
|
||||
|
||||
if self.type == CONTENT_TYPE_IMAGE_PNG:
|
||||
image.load_png_from_buffer(data)
|
||||
|
||||
elif self.type == CONTENT_TYPE_IMAGE_JPEG:
|
||||
image.load_jpg_from_buffer(data)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
class BaseEmote:
|
||||
const TEXTURE_NO_FLAGS = 0
|
||||
|
||||
static func create_texture_from_image(image:Image)->ImageTexture:
|
||||
var image_texture: = ImageTexture.new()
|
||||
image_texture.create_from_image(image)
|
||||
image_texture.flags -= ImageTexture.FLAG_FILTER + ImageTexture.FLAG_REPEAT
|
||||
|
||||
return image_texture
|
||||
|
||||
|
||||
|
||||
func _ready()->void :
|
||||
__initialize()
|
||||
__initialize_http_request_queue()
|
||||
__connect_signals()
|
||||
|
||||
func _downloaded(downloaded_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
"
\n Override to define behaviour on emote content downloaded.
\n "
|
||||
pass
|
||||
|
||||
func _get_emote_url(code:String)->String:
|
||||
"
\n Override to prepare the emote retrieval URL by code.
\n "
|
||||
return ""
|
||||
|
||||
|
||||
func __initialize()->void :
|
||||
"
\n Override for initialization, instead of _ready.
\n "
|
||||
pass
|
||||
|
||||
func __connect_signals()->void :
|
||||
http_request_queue.connect("request_completed", self, "_on_http_request_queue_request_complete")
|
||||
|
||||
func __initialize_http_request_queue()->void :
|
||||
http_request_queue = HttpRequestQueue.new()
|
||||
|
||||
add_child(http_request_queue)
|
||||
|
||||
func __cache_emote(code)->void :
|
||||
var url:String = _get_emote_url(code)
|
||||
|
||||
__download(code, url)
|
||||
|
||||
func __download(id:String, url:String)->void :
|
||||
http_request_queue.enqueue_request(id, url)
|
||||
|
||||
|
||||
|
||||
func _on_http_request_queue_request_complete(id:String, result:int, response_code:int, headers:PoolStringArray, body:PoolByteArray)->void :
|
||||
var downloaded_content: = DownloadedContent.new(id, "", body)
|
||||
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
var pretty_headers: = HttpHeaders.new(headers)
|
||||
var content_type: = pretty_headers.headers.get("Content-Type") as String
|
||||
|
||||
downloaded_content.type = content_type
|
||||
|
||||
|
||||
|
||||
_downloaded(downloaded_content)
|
112
addons/godot-twicil/helpers/cache/bttv_emotes_cache.gd
vendored
Normal file
112
addons/godot-twicil/helpers/cache/bttv_emotes_cache.gd
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
extends BaseEmotesCache
|
||||
class_name BttvEmotesCache
|
||||
|
||||
|
||||
signal emote_retrieved(emote)
|
||||
|
||||
|
||||
class BttvEmote:
|
||||
var id:String
|
||||
var code:String
|
||||
var texture:ImageTexture
|
||||
|
||||
func _init(id:String, code:String, image:Image):
|
||||
self.id = id
|
||||
self.code = code
|
||||
self.texture = BaseEmotesCache.BaseEmote.create_texture_from_image(image)
|
||||
|
||||
|
||||
const DEFAULT_URL_PROTOCOL = "https://"
|
||||
const CHANNEL_NAME_PLACEHOLDER = "{{channel_name}}"
|
||||
const EMOTE_ID_PLACEHOLDER = "{{id}}"
|
||||
const EMOTE_SIZE_PLACEHOLDER = "{{image}}"
|
||||
|
||||
|
||||
const DEAFULT_EMOTE_IMAGE_SIZE = "1x"
|
||||
|
||||
const GLOBAL_EMOTES_REQUEST_ID = "global_emotes"
|
||||
const CHANNEL_EMOTES_REQUEST_ID = "channel_emotes"
|
||||
const EMOTES_REQUEST_IDS = [GLOBAL_EMOTES_REQUEST_ID, CHANNEL_EMOTES_REQUEST_ID]
|
||||
|
||||
const GLOBAL_EMOTES_URL = "https://api.betterttv.net/2/emotes/"
|
||||
const CHANNEL_EMOTES_URL_TEMPLATE = "https://api.betterttv.net/2/channels/{{channel_name}}/"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var available_emotes: = Dictionary()
|
||||
var emote_download_url_template:String
|
||||
var cache: = Dictionary()
|
||||
var available_emotes_parsed_count: = 0
|
||||
|
||||
|
||||
func _downloaded(downloaded_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
if downloaded_content.id in EMOTES_REQUEST_IDS:
|
||||
__parse_available_emotes(downloaded_content)
|
||||
|
||||
available_emotes_parsed_count += 1
|
||||
ready_to_deliver_emotes = available_emotes_parsed_count >= EMOTES_REQUEST_IDS.size()
|
||||
|
||||
else :
|
||||
var code:String = str(downloaded_content.id)
|
||||
var id:String = available_emotes.get(code)
|
||||
var image:Image = downloaded_content.get_image_from_data()
|
||||
|
||||
cache[code] = BttvEmote.new(id, code, image)
|
||||
|
||||
emit_signal("emote_retrieved", cache.get(code))
|
||||
|
||||
func _get_emote_url(code:String)->String:
|
||||
var id:String = available_emotes.get(code)
|
||||
|
||||
if not id:
|
||||
return ""
|
||||
|
||||
var url: = emote_download_url_template.replace(
|
||||
EMOTE_ID_PLACEHOLDER, id
|
||||
).replace(
|
||||
EMOTE_SIZE_PLACEHOLDER, DEAFULT_EMOTE_IMAGE_SIZE
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
func init_emotes(channel_name:String)->void :
|
||||
http_request_queue.enqueue_request(GLOBAL_EMOTES_REQUEST_ID, GLOBAL_EMOTES_URL)
|
||||
http_request_queue.enqueue_request(
|
||||
CHANNEL_EMOTES_REQUEST_ID,
|
||||
CHANNEL_EMOTES_URL_TEMPLATE.replace(CHANNEL_NAME_PLACEHOLDER, channel_name)
|
||||
)
|
||||
|
||||
|
||||
func get_emote(code:String)->void :
|
||||
if not ready_to_deliver_emotes:
|
||||
return
|
||||
|
||||
if cache.has(code):
|
||||
emit_signal("emote_retrieved", cache.get(code))
|
||||
|
||||
else :
|
||||
__cache_emote(code)
|
||||
|
||||
func get_available_emotes_codes()->Array:
|
||||
return available_emotes.keys()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func __parse_available_emotes(download_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
if download_content.type != HttpHeaders.HTTP_CONTENT_TYPE_JSON_UTF8:
|
||||
return
|
||||
|
||||
var data = parse_json(download_content.data.get_string_from_utf8())
|
||||
|
||||
emote_download_url_template = data.get("urlTemplate", "").replace("//", DEFAULT_URL_PROTOCOL)
|
||||
|
||||
for emote in data.get("emotes", []):
|
||||
available_emotes[emote.get("code")] = emote.get("id")
|
117
addons/godot-twicil/helpers/cache/ffz_emotes_cache.gd
vendored
Normal file
117
addons/godot-twicil/helpers/cache/ffz_emotes_cache.gd
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
extends BaseEmotesCache
|
||||
class_name FfzEmotesCache
|
||||
|
||||
|
||||
signal emote_retrieved(emote)
|
||||
|
||||
|
||||
class FfzEmote:
|
||||
var id:String
|
||||
var code:String
|
||||
var texture:ImageTexture
|
||||
|
||||
func _init(id:String, code:String, image:Image):
|
||||
self.id = id
|
||||
self.code = code
|
||||
self.texture = BaseEmotesCache.BaseEmote.create_texture_from_image(image)
|
||||
|
||||
|
||||
const DEFAULT_URL_PROTOCOL = "https://"
|
||||
const USER_ID_PLACEHOLDER = "{{user_id}}"
|
||||
const EMOTE_ID_PLACEHOLDER = "{{id}}"
|
||||
const EMOTE_SIZE_PLACEHOLDER = "{{image}}"
|
||||
|
||||
|
||||
const DEAFULT_EMOTE_IMAGE_SIZE = "1x"
|
||||
|
||||
const GLOBAL_EMOTES_REQUEST_ID = "global_emotes"
|
||||
const CHANNEL_EMOTES_REQUEST_ID = "channel_emotes"
|
||||
const EMOTES_REQUEST_IDS = [GLOBAL_EMOTES_REQUEST_ID, CHANNEL_EMOTES_REQUEST_ID]
|
||||
|
||||
const GLOBAL_EMOTES_URL = "https://api.frankerfacez.com/v1/set/global"
|
||||
const CHANNEL_EMOTES_URL_TEMPLATE = "https://api.frankerfacez.com/v1/room/id/{{user_id}}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var available_emotes: = Dictionary()
|
||||
var cache: = Dictionary()
|
||||
var available_emotes_parsed_count: = 0
|
||||
var user_id:String
|
||||
|
||||
|
||||
func _downloaded(downloaded_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
if downloaded_content.id in EMOTES_REQUEST_IDS:
|
||||
__parse_available_emotes(downloaded_content)
|
||||
|
||||
available_emotes_parsed_count += 1
|
||||
ready_to_deliver_emotes = available_emotes_parsed_count >= EMOTES_REQUEST_IDS.size()
|
||||
|
||||
else :
|
||||
var code:String = str(downloaded_content.id)
|
||||
var id:String = available_emotes.get(code, {}).get("id")
|
||||
var image:Image = downloaded_content.get_image_from_data()
|
||||
|
||||
cache[code] = FfzEmote.new(id, code, image)
|
||||
|
||||
emit_signal("emote_retrieved", cache.get(code))
|
||||
|
||||
func _get_emote_url(code:String)->String:
|
||||
var url:String = available_emotes.get(code, {}).get("url", "")
|
||||
|
||||
return url
|
||||
|
||||
|
||||
func init_emotes(user_id:String, force:bool = false)->void :
|
||||
if self.user_id == null or self.user_id != user_id or force:
|
||||
user_id = user_id
|
||||
http_request_queue.enqueue_request(GLOBAL_EMOTES_REQUEST_ID, GLOBAL_EMOTES_URL)
|
||||
http_request_queue.enqueue_request(
|
||||
CHANNEL_EMOTES_REQUEST_ID,
|
||||
CHANNEL_EMOTES_URL_TEMPLATE.replace(USER_ID_PLACEHOLDER, user_id)
|
||||
)
|
||||
|
||||
|
||||
func get_emote(code:String)->void :
|
||||
if not ready_to_deliver_emotes:
|
||||
return
|
||||
|
||||
if cache.has(code):
|
||||
emit_signal("emote_retrieved", cache.get(code))
|
||||
|
||||
else :
|
||||
__cache_emote(code)
|
||||
|
||||
func get_available_emotes_codes()->Array:
|
||||
return available_emotes.keys()
|
||||
|
||||
|
||||
|
||||
func __parse_available_emotes(download_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
if download_content.type != HttpHeaders.HTTP_CONTENT_TYPE_JSON:
|
||||
return
|
||||
|
||||
var data = parse_json(download_content.data.get_string_from_utf8())
|
||||
var sets: = data.get("sets") as Dictionary
|
||||
|
||||
for set in sets.values():
|
||||
var emotes: = set.get("emoticons") as Array
|
||||
|
||||
for emote in emotes:
|
||||
var emote_url:String = emote.get("urls", {}).get("1", "").replace(
|
||||
"//", DEFAULT_URL_PROTOCOL
|
||||
)
|
||||
|
||||
var id: = str(emote.get("id"), "")
|
||||
|
||||
available_emotes[emote.get("name")] = {
|
||||
"id":id,
|
||||
"url":emote_url
|
||||
}
|
59
addons/godot-twicil/helpers/cache/twitch_emotes_cache.gd
vendored
Normal file
59
addons/godot-twicil/helpers/cache/twitch_emotes_cache.gd
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
extends BaseEmotesCache
|
||||
class_name TwitchEmotesCache
|
||||
|
||||
|
||||
signal emote_retrieved(emote)
|
||||
|
||||
|
||||
class TwitchEmote:
|
||||
var id:int
|
||||
var code:String
|
||||
var texture:ImageTexture
|
||||
|
||||
func _init(id:int, code:String, image:Image):
|
||||
self.id = id
|
||||
self.code = code
|
||||
self.texture = BaseEmotesCache.BaseEmote.create_texture_from_image(image)
|
||||
|
||||
|
||||
const EMOTE_URL_TEMPLATE = "https://static-cdn.jtvnw.net/emoticons/v1/{emote_id}/1.0"
|
||||
|
||||
|
||||
|
||||
|
||||
var cache: = Dictionary()
|
||||
|
||||
|
||||
|
||||
func _ready():
|
||||
._ready()
|
||||
|
||||
ready_to_deliver_emotes = true
|
||||
|
||||
|
||||
|
||||
func get_emote(id:int):
|
||||
if not ready_to_deliver_emotes:
|
||||
return
|
||||
|
||||
if cache.has(id):
|
||||
emit_signal("emote_retrieved", cache.get(id))
|
||||
|
||||
else :
|
||||
__cache_emote(str(id))
|
||||
|
||||
|
||||
|
||||
func _get_emote_url(code)->String:
|
||||
var string_id: = str(code)
|
||||
var url: = EMOTE_URL_TEMPLATE.replace("{emote_id}", string_id)
|
||||
|
||||
return url
|
||||
|
||||
func _downloaded(downloaded_content:BaseEmotesCache.DownloadedContent)->void :
|
||||
var id_: = int(downloaded_content.id)
|
||||
var image:Image = downloaded_content.get_image_from_data()
|
||||
|
||||
cache[id_] = TwitchEmote.new(id_, "", image)
|
||||
|
||||
emit_signal("emote_retrieved", cache.get(id_))
|
32
addons/godot-twicil/helpers/chat_list.gd
Normal file
32
addons/godot-twicil/helpers/chat_list.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name ChatList
|
||||
|
||||
|
||||
var __list: = Dictionary()
|
||||
|
||||
func add_user(name:String)->void :
|
||||
if name in __list:
|
||||
return
|
||||
|
||||
__list[name] = ChatUser.new(name)
|
||||
|
||||
func remove_user(name:String)->void :
|
||||
if name in __list:
|
||||
__list.erase(name)
|
||||
|
||||
func get_user_details(name:String)->ChatUser:
|
||||
if name in __list:
|
||||
return __list[name] as ChatUser
|
||||
|
||||
return null
|
||||
|
||||
func get_names()->Array:
|
||||
return __list.keys()
|
||||
|
||||
func size()->int:
|
||||
return __list.size()
|
||||
|
||||
func clear()->void :
|
||||
__list.clear()
|
||||
|
||||
func has(name:String)->bool:
|
||||
return name in __list
|
8
addons/godot-twicil/helpers/chat_user.gd
Normal file
8
addons/godot-twicil/helpers/chat_user.gd
Normal file
|
@ -0,0 +1,8 @@
|
|||
class_name ChatUser
|
||||
|
||||
|
||||
var name:String
|
||||
|
||||
|
||||
func _init(name:String):
|
||||
self.name = name
|
33
addons/godot-twicil/helpers/http/http_headers.gd
Normal file
33
addons/godot-twicil/helpers/http/http_headers.gd
Normal file
|
@ -0,0 +1,33 @@
|
|||
extends Object
|
||||
class_name HttpHeaders
|
||||
|
||||
const HTTP_CONTENT_TYPE_JSON_UTF8 = "application/json; charset=utf-8"
|
||||
const HTTP_CONTENT_TYPE_JSON = "application/json"
|
||||
|
||||
|
||||
var headers:Dictionary
|
||||
|
||||
func _init(raw_headers:PoolStringArray):
|
||||
for raw_header in raw_headers:
|
||||
var header_parts: = (raw_header as String).split(":", true, 1) as Array
|
||||
var header_name: = (header_parts[0] as String).lstrip(" ").rstrip(" ")
|
||||
var header_value: = (header_parts[1] as String).lstrip(" ").rstrip(" ")
|
||||
|
||||
headers[header_name] = header_value
|
||||
|
||||
func get(key:String, ignore_case:bool = true)->String:
|
||||
for header_key in headers:
|
||||
if header_key.to_lower() == key.to_lower():
|
||||
return headers.get(header_key)
|
||||
|
||||
return "{no such header}"
|
||||
|
||||
static func to_pool_string_array(headers:Dictionary)->PoolStringArray:
|
||||
var raw_headers:PoolStringArray
|
||||
|
||||
for header in headers:
|
||||
var header_value:String = headers.get(header)
|
||||
|
||||
raw_headers.append(header + ": " + header_value)
|
||||
|
||||
return raw_headers
|
78
addons/godot-twicil/helpers/http/http_request_queue.gd
Normal file
78
addons/godot-twicil/helpers/http/http_request_queue.gd
Normal file
|
@ -0,0 +1,78 @@
|
|||
extends Node
|
||||
class_name HttpRequestQueue
|
||||
|
||||
|
||||
signal http_response_recieved(content_type, body)
|
||||
signal http_response_failed(error_code)
|
||||
signal request_completed(id, result, response_code, headers, body)
|
||||
signal request_completed_ex(id, result, response_code, http_headers, body)
|
||||
|
||||
|
||||
const REQUEST_ID_NO_ID = "{no_id}"
|
||||
|
||||
|
||||
var _http_request:HTTPRequest
|
||||
var request_queue = Array()
|
||||
var busy = false
|
||||
var current_request_id:String = REQUEST_ID_NO_ID
|
||||
|
||||
|
||||
func _ready()->void :
|
||||
__initialize_http_request()
|
||||
|
||||
|
||||
func enqueue_request(id:String, url:String, headers:PoolStringArray = PoolStringArray())->void :
|
||||
request_queue.append({"id":id, "url":url, "headers":headers})
|
||||
|
||||
if not busy:
|
||||
__process_request_queue()
|
||||
|
||||
|
||||
func __initialize_http_request()->void :
|
||||
_http_request = HTTPRequest.new()
|
||||
|
||||
add_child(_http_request)
|
||||
|
||||
_http_request.use_threads = true
|
||||
|
||||
_http_request.connect("request_completed", self, "_on_http_request_completed")
|
||||
|
||||
|
||||
func __process_request_queue()->void :
|
||||
if request_queue.empty():
|
||||
busy = false
|
||||
|
||||
return
|
||||
|
||||
if busy:
|
||||
return
|
||||
|
||||
busy = true
|
||||
|
||||
var request_data: = request_queue.pop_front() as Dictionary
|
||||
var request_url:String = request_data.get("url")
|
||||
var request_headers:PoolStringArray = request_data.get("headers")
|
||||
current_request_id = request_data.get("id")
|
||||
|
||||
_http_request.request(request_url, request_headers)
|
||||
|
||||
|
||||
|
||||
func _on_http_request_completed(result:int, response_code:int, headers:PoolStringArray, body:PoolByteArray)->void :
|
||||
var http_headers: = HttpHeaders.new(headers)
|
||||
emit_signal("request_completed", current_request_id, result, response_code, headers, body)
|
||||
emit_signal("request_completed_ex", current_request_id, result, response_code, http_headers, body)
|
||||
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
|
||||
var content_type: = http_headers.get("Content-Type") as String
|
||||
|
||||
emit_signal("http_response_recieved", content_type, body)
|
||||
|
||||
else :
|
||||
emit_signal("http_response_failed", response_code)
|
||||
|
||||
current_request_id = REQUEST_ID_NO_ID
|
||||
busy = false
|
||||
|
||||
__process_request_queue()
|
67
addons/godot-twicil/helpers/interactive_commands.gd
Normal file
67
addons/godot-twicil/helpers/interactive_commands.gd
Normal file
|
@ -0,0 +1,67 @@
|
|||
class_name InteractiveCommands
|
||||
|
||||
|
||||
class FuncRefEx extends FuncRef:
|
||||
func _init(instance:Object, method:String):
|
||||
.set_instance(instance)
|
||||
.set_function(method)
|
||||
|
||||
class InteractiveCommand:
|
||||
var func_ref:FuncRef
|
||||
var params_count:int
|
||||
var variable_params_count:int
|
||||
|
||||
func _init(func_ref:FuncRef, params_count:int, variable_params_count:bool = false):
|
||||
self.func_ref = func_ref
|
||||
self.params_count = params_count
|
||||
self.variable_params_count = variable_params_count
|
||||
|
||||
func call_command(params:Array)->void :
|
||||
func_ref.call_func(params)
|
||||
|
||||
var interactive_commands = {}
|
||||
|
||||
|
||||
func add(
|
||||
chat_command:String,
|
||||
target:Object,
|
||||
method_name:String,
|
||||
params_count:int = 1,
|
||||
variable_params_count:bool = false
|
||||
)->void :
|
||||
interactive_commands[chat_command] = InteractiveCommand.new(
|
||||
FuncRefEx.new(target, method_name) as FuncRef, params_count, variable_params_count)
|
||||
|
||||
func add_aliases(chat_command:String, new_aliases:Array)->void :
|
||||
if interactive_commands.has(chat_command):
|
||||
for new_alias in new_aliases:
|
||||
interactive_commands[new_alias] = interactive_commands[chat_command]
|
||||
|
||||
func remove(chat_command:String)->void :
|
||||
if interactive_commands.has(chat_command):
|
||||
interactive_commands[chat_command]
|
||||
interactive_commands.erase(chat_command)
|
||||
|
||||
|
||||
func _on_message_recieved(sender:String, text:String, emotes:Array)->void :
|
||||
var input_cmd:Array = text.split(" ")
|
||||
|
||||
for cmd in interactive_commands:
|
||||
if input_cmd[0] == cmd:
|
||||
|
||||
if not interactive_commands[cmd].variable_params_count and input_cmd.size() - 1 < interactive_commands[cmd].params_count:
|
||||
|
||||
return
|
||||
|
||||
var params:Array = [sender]
|
||||
var params_count:int = clamp(
|
||||
input_cmd.size() - 1,
|
||||
0,
|
||||
interactive_commands[cmd].params_count
|
||||
)
|
||||
|
||||
if params_count >= 1:
|
||||
for i in range(params_count):
|
||||
params.append(input_cmd[i + 1])
|
||||
|
||||
interactive_commands[cmd].call_command(params)
|
9
addons/godot-twicil/helpers/irc_chat_message.gd
Normal file
9
addons/godot-twicil/helpers/irc_chat_message.gd
Normal file
|
@ -0,0 +1,9 @@
|
|||
class_name IrcChatMessage
|
||||
|
||||
|
||||
var name:String
|
||||
var text:String
|
||||
|
||||
func _init(name:String, text:String):
|
||||
self.name = name
|
||||
self.text = text
|
148
addons/godot-twicil/helpers/irc_client_ex.gd
Normal file
148
addons/godot-twicil/helpers/irc_client_ex.gd
Normal file
|
@ -0,0 +1,148 @@
|
|||
extends Node
|
||||
class_name IrcClientEx
|
||||
|
||||
signal response_recieved(response)
|
||||
signal http_response_recieved(type, response)
|
||||
signal http_response_failed(error_code)
|
||||
|
||||
export (float) var CONNECT_WAIT_TIMEOUT = 1
|
||||
export (float) var COMMAND_WAIT_TIMEOUT = 0.3
|
||||
|
||||
onready var __stream_peer = StreamPeerTCP.new()
|
||||
onready var queue: = Queue.new()
|
||||
|
||||
|
||||
var http_request_queue:HttpRequestQueue
|
||||
|
||||
|
||||
|
||||
var processing = false
|
||||
|
||||
var _host:String
|
||||
var _port:int
|
||||
|
||||
var __time_passed: = 0.0
|
||||
var __last_command_time: = 0.0
|
||||
|
||||
var __log: = false
|
||||
|
||||
|
||||
|
||||
func set_logging(state:bool)->void :
|
||||
__log = state
|
||||
|
||||
func connect_to_host(host:String, port:int)->bool:
|
||||
_host = host
|
||||
_port = port
|
||||
|
||||
return __stream_peer.connect_to_host(_host, _port) == OK
|
||||
|
||||
func send_command(command:String)->void :
|
||||
queue.append(command)
|
||||
|
||||
func abort_processing()->void :
|
||||
processing = false
|
||||
|
||||
func Disconnect():
|
||||
__stream_peer.disconnect_from_host();
|
||||
|
||||
func IsConnected()->bool:
|
||||
if __stream_peer == null:
|
||||
return false;
|
||||
|
||||
return __stream_peer.connect_to_host(_host, _port) == OK;
|
||||
|
||||
|
||||
func _log(text:String)->void :
|
||||
if __log:
|
||||
prints("[%s] %s" % [__get_time_str(), text])
|
||||
|
||||
func __get_time_str()->String:
|
||||
var time = OS.get_time()
|
||||
return str(time.hour, ":", time.minute, ":", time.second)
|
||||
|
||||
func __send_command(command:String)->void :
|
||||
var command_chunck_bytes: = PoolByteArray()
|
||||
var chunck_size: = 8
|
||||
var chuncks_count:int = command.length() / chunck_size
|
||||
var appendix_length:int = command.length() % chunck_size
|
||||
|
||||
_log("<< %s" % command)
|
||||
|
||||
|
||||
for i in range(chuncks_count):
|
||||
command_chunck_bytes = command.substr(i * chunck_size, chunck_size).to_utf8()
|
||||
__stream_peer.put_data(command_chunck_bytes)
|
||||
|
||||
if appendix_length > 0:
|
||||
command_chunck_bytes = command.substr(chunck_size * chuncks_count, appendix_length).to_utf8()
|
||||
__stream_peer.put_data(command_chunck_bytes)
|
||||
|
||||
command_chunck_bytes = ("
\n").to_utf8()
|
||||
__stream_peer.put_data(command_chunck_bytes)
|
||||
|
||||
func __process()->void :
|
||||
while processing:
|
||||
__process_commands()
|
||||
__process_input()
|
||||
|
||||
func __process_commands()->void :
|
||||
if queue.is_empty() or __time_passed - __last_command_time < COMMAND_WAIT_TIMEOUT:
|
||||
return
|
||||
|
||||
__send_command(queue.pop_next() as String)
|
||||
|
||||
__last_command_time = __time_passed
|
||||
|
||||
func __process_input()->void :
|
||||
if not __stream_peer.is_connected_to_host():
|
||||
return ;
|
||||
|
||||
var bytes_available:int = __stream_peer.get_available_bytes()
|
||||
|
||||
if not (__stream_peer.is_connected_to_host() and bytes_available > 0):
|
||||
return
|
||||
|
||||
var data: = __stream_peer.get_utf8_string(bytes_available) as String
|
||||
|
||||
_log(">> %s" % data)
|
||||
|
||||
emit_signal("response_recieved", data)
|
||||
|
||||
func __parse_server_message(data):
|
||||
pass
|
||||
|
||||
func __initialize_http_request_queue()->void :
|
||||
http_request_queue = HttpRequestQueue.new()
|
||||
add_child(http_request_queue)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func _ready()->void :
|
||||
set_process(true)
|
||||
|
||||
__initialize_http_request_queue()
|
||||
|
||||
|
||||
|
||||
func _process(delta:float)->void :
|
||||
__time_passed += delta
|
||||
|
||||
processing = __time_passed > CONNECT_WAIT_TIMEOUT
|
||||
|
||||
if not processing:
|
||||
return
|
||||
|
||||
__process_commands()
|
||||
|
||||
__process_input()
|
||||
|
||||
|
||||
|
||||
func _on_http_response_recieved(content_type:String, data:PoolByteArray)->void :
|
||||
emit_signal("http_response_recieved", content_type, data)
|
||||
|
||||
func _on_http_response_failed(error_code:int)->void :
|
||||
emit_signal("http_response_failed", error_code)
|
161
addons/godot-twicil/helpers/irc_client_scure.gd
Normal file
161
addons/godot-twicil/helpers/irc_client_scure.gd
Normal file
|
@ -0,0 +1,161 @@
|
|||
extends Node
|
||||
class_name IrcClientSecure
|
||||
|
||||
signal response_recieved(response)
|
||||
signal http_response_recieved(type, response)
|
||||
signal http_response_failed(error_code)
|
||||
|
||||
signal ConnectedToTwitch;
|
||||
signal DisconnectedFromTwitch;
|
||||
|
||||
|
||||
export (float) var CONNECT_WAIT_TIMEOUT = 2.0
|
||||
export (float) var COMMAND_WAIT_TIMEOUT = 0.3
|
||||
|
||||
onready var __websocket_client = WebSocketClient.new();
|
||||
onready var command_queue: = Array()
|
||||
|
||||
|
||||
var http_request_queue:HttpRequestQueue
|
||||
var __websocket_peer:WebSocketPeer
|
||||
|
||||
var processing = false
|
||||
|
||||
var _host:String
|
||||
var _port:int
|
||||
|
||||
var __time_passed: = 0.0
|
||||
var __last_command_time: = 0.0
|
||||
var connection_status:int = - 1
|
||||
|
||||
var __log: = false
|
||||
|
||||
var isConnected: = false;
|
||||
|
||||
|
||||
func set_logging(state:bool)->void :
|
||||
__log = state
|
||||
|
||||
func connect_to_host(host:String, port:int)->bool:
|
||||
_host = host
|
||||
_port = port
|
||||
|
||||
|
||||
|
||||
var result:int = __websocket_client.connect_to_url(str(_host, ":", _port))
|
||||
|
||||
__websocket_peer = __websocket_client.get_peer(1)
|
||||
|
||||
set_process(true)
|
||||
__websocket_peer.set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
|
||||
|
||||
return result == OK
|
||||
|
||||
func send_command(command:String)->void :
|
||||
command_queue.append(command)
|
||||
|
||||
func Disconnect():
|
||||
__websocket_client.disconnect_from_host();
|
||||
__websocket_peer.close();
|
||||
|
||||
func IsConnected()->bool:
|
||||
return isConnected;
|
||||
|
||||
|
||||
func _log(text:String)->void :
|
||||
if __log:
|
||||
prints("[%s] %s" % [__get_time_str(), text])
|
||||
|
||||
func __get_time_str()->String:
|
||||
var time = OS.get_time()
|
||||
return str(time.hour, ":", time.minute, ":", time.second)
|
||||
|
||||
func __send_command(command:String)->int:
|
||||
var result:int = __websocket_peer.put_packet(command.to_utf8())
|
||||
|
||||
return result
|
||||
|
||||
func __process_commands()->void :
|
||||
var next_command_time:bool = __time_passed - __last_command_time >= COMMAND_WAIT_TIMEOUT
|
||||
|
||||
if command_queue.empty() or not next_command_time:
|
||||
return
|
||||
|
||||
__send_command(command_queue.pop_front() as String)
|
||||
|
||||
__last_command_time = __time_passed
|
||||
|
||||
func __process_incoming_data()->void :
|
||||
var available_packets_count: = __websocket_peer.get_available_packet_count()
|
||||
|
||||
var recieved_string:String = ""
|
||||
while available_packets_count > 0:
|
||||
var packet = __websocket_peer.get_packet()
|
||||
recieved_string += packet.get_string_from_utf8()
|
||||
|
||||
available_packets_count -= 1
|
||||
|
||||
if recieved_string:
|
||||
_log(">> %s" % recieved_string)
|
||||
|
||||
emit_signal("response_recieved", recieved_string)
|
||||
|
||||
func __parse_server_message(data):
|
||||
pass
|
||||
|
||||
func __initialize_http_request_queue()->void :
|
||||
http_request_queue = HttpRequestQueue.new()
|
||||
add_child(http_request_queue)
|
||||
|
||||
|
||||
|
||||
func _ready()->void :
|
||||
set_process(false)
|
||||
|
||||
__initialize_http_request_queue()
|
||||
|
||||
func _process(delta:float)->void :
|
||||
__time_passed += delta
|
||||
|
||||
if __websocket_client.get_connection_status() != connection_status:
|
||||
connection_status = __websocket_client.get_connection_status()
|
||||
|
||||
if connection_status == WebSocketClient.CONNECTION_CONNECTING:
|
||||
_log("Connecting to server...")
|
||||
|
||||
if connection_status == WebSocketClient.CONNECTION_CONNECTED:
|
||||
isConnected = true;
|
||||
emit_signal("ConnectedToTwitch");
|
||||
_log("Connected.")
|
||||
print("connected")
|
||||
|
||||
if connection_status == WebSocketClient.CONNECTION_DISCONNECTED:
|
||||
isConnected = false;
|
||||
emit_signal("DisconnectedFromTwitch");
|
||||
_log("Disconnected.")
|
||||
print("disconnected")
|
||||
__websocket_client = null;
|
||||
__websocket_client = WebSocketClient.new();
|
||||
__websocket_client.set_verify_ssl_enabled(false)
|
||||
|
||||
|
||||
var is_connecting:bool = connection_status == WebSocketClient.CONNECTION_CONNECTING
|
||||
var is_connected:bool = connection_status == WebSocketClient.CONNECTION_CONNECTED
|
||||
|
||||
if is_connecting or is_connected:
|
||||
__websocket_client.poll()
|
||||
|
||||
var is_peer_connected:bool = __websocket_peer.is_connected_to_host()
|
||||
|
||||
if is_peer_connected and __time_passed > CONNECT_WAIT_TIMEOUT:
|
||||
__process_commands()
|
||||
|
||||
__process_incoming_data()
|
||||
|
||||
|
||||
|
||||
func _on_http_response_recieved(content_type:String, data:PoolByteArray)->void :
|
||||
emit_signal("http_response_recieved", content_type, data)
|
||||
|
||||
func _on_http_response_failed(error_code:int)->void :
|
||||
emit_signal("http_response_failed", error_code)
|
14
addons/godot-twicil/helpers/message_wrapper.gd
Normal file
14
addons/godot-twicil/helpers/message_wrapper.gd
Normal file
|
@ -0,0 +1,14 @@
|
|||
class_name MessageWrapper
|
||||
|
||||
|
||||
static func wrap(server_irc_message:TwitchIrcServerMessage)->IrcChatMessage:
|
||||
var res = IrcChatMessage.new("", "")
|
||||
|
||||
res.name = get_sender_name(server_irc_message)
|
||||
res.text = server_irc_message.params[1]
|
||||
res.text = res.text.substr(1, res.text.length() - 1)
|
||||
|
||||
return res
|
||||
|
||||
static func get_sender_name(server_irc_message:TwitchIrcServerMessage)->String:
|
||||
return server_irc_message.prefix.split("!")[0]
|
32
addons/godot-twicil/helpers/queue.gd
Normal file
32
addons/godot-twicil/helpers/queue.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
extends Object
|
||||
class_name Queue
|
||||
|
||||
var __queue = []
|
||||
var busy = false
|
||||
|
||||
func append(element)->void :
|
||||
if busy:
|
||||
return
|
||||
|
||||
busy = true
|
||||
|
||||
__queue.append(element)
|
||||
|
||||
busy = false
|
||||
|
||||
func pop_next():
|
||||
if busy:
|
||||
return
|
||||
|
||||
busy = true
|
||||
|
||||
var element = __queue[0]
|
||||
|
||||
__queue.pop_front()
|
||||
|
||||
busy = false
|
||||
|
||||
return element
|
||||
|
||||
func is_empty()->bool:
|
||||
return __queue.size() == 0
|
46
addons/godot-twicil/helpers/tools.gd
Normal file
46
addons/godot-twicil/helpers/tools.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
class_name HelperTools
|
||||
|
||||
|
||||
func __equals_string(str1:String, str2:String)->bool:
|
||||
return str1 == str2
|
||||
|
||||
func __equals_one_of_strings(str1:String, str_list:Array)->bool:
|
||||
return str1 in str_list
|
||||
|
||||
func split_string(string:String, splitter, splits_count:int = 0):
|
||||
var res:Array = []
|
||||
var curr_substring: = ""
|
||||
var occurances: = 0
|
||||
var splitter_length: = 1
|
||||
|
||||
var matches: = FuncRef.new()
|
||||
matches.set_instance(self)
|
||||
|
||||
if typeof(splitter) == TYPE_STRING:
|
||||
matches.set_function("__equals_string")
|
||||
splitter_length = splitter.length()
|
||||
|
||||
elif typeof(splitter) == TYPE_ARRAY:
|
||||
matches.set_function("__equals_one_of_strings")
|
||||
|
||||
for i in range(string.length()):
|
||||
if matches.call_func(string.substr(i, splitter_length), splitter):
|
||||
|
||||
|
||||
|
||||
res.append(curr_substring)
|
||||
|
||||
curr_substring = ""
|
||||
|
||||
occurances += 1
|
||||
if splits_count > 0 and occurances == splits_count:
|
||||
res.append(string.substr(i + 1, string.length() - i - 1))
|
||||
return res
|
||||
|
||||
continue
|
||||
|
||||
curr_substring += string[i]
|
||||
|
||||
res.append(curr_substring)
|
||||
|
||||
return res
|
13
addons/godot-twicil/helpers/twitch_irc_server_message.gd
Normal file
13
addons/godot-twicil/helpers/twitch_irc_server_message.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
class_name TwitchIrcServerMessage
|
||||
|
||||
|
||||
var message_prefix:String
|
||||
var prefix:String
|
||||
var command:String
|
||||
var params:Array
|
||||
|
||||
func _init(message_prefix:String, prefix:String, command:String, params:Array):
|
||||
self.message_prefix = message_prefix
|
||||
self.prefix = prefix
|
||||
self.command = command
|
||||
self.params = params
|
88
addons/godot-twicil/helpers/twitch_message_wrapper.gd
Normal file
88
addons/godot-twicil/helpers/twitch_message_wrapper.gd
Normal file
|
@ -0,0 +1,88 @@
|
|||
class_name TwitchMessage
|
||||
|
||||
enum EmoteType{TWITCH, BTTV, FFZ}
|
||||
|
||||
const emote_id_methods = {
|
||||
EmoteType.BTTV:"__get_bttv_emote_id",
|
||||
EmoteType.FFZ:"__get_ffz_emote_id"
|
||||
}
|
||||
|
||||
|
||||
var chat_message:IrcChatMessage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var emotes:Array
|
||||
|
||||
|
||||
func _init(server_irc_message:TwitchIrcServerMessage, bttv_emotes:Dictionary, ffz_emotes):
|
||||
chat_message = MessageWrapper.wrap(server_irc_message)
|
||||
|
||||
emotes.clear()
|
||||
|
||||
__parse_twitch_emotes(server_irc_message.message_prefix)
|
||||
__parse_bttv_emotes(bttv_emotes)
|
||||
__parse_ffz_emotes(ffz_emotes)
|
||||
|
||||
|
||||
|
||||
func __parse_twitch_emotes(message_prefix:String):
|
||||
var prefix_params: = message_prefix.split(";", false)
|
||||
var emotes_param:String
|
||||
|
||||
for param in prefix_params:
|
||||
if (param as String).begins_with("emotes"):
|
||||
var emotes_prefix_param:Array = (param as String).split("=", false, 1)
|
||||
|
||||
if emotes_prefix_param.size() <= 1:
|
||||
return
|
||||
|
||||
emotes_param = emotes_prefix_param[1]
|
||||
|
||||
for emote in emotes_param.split("/", false):
|
||||
var emote_data:Array = emote.split(":", false)
|
||||
var id: = int(emote_data[0])
|
||||
|
||||
var positions:Array = emote_data[1].split(",", false)[0].split("-", false)
|
||||
|
||||
var start: = int(positions[0])
|
||||
var end: = int(positions[1])
|
||||
|
||||
var code:String = chat_message.text.substr(start, end - start + 1)
|
||||
|
||||
emotes.append({
|
||||
"id":id,
|
||||
"code":code,
|
||||
"type":EmoteType.TWITCH
|
||||
})
|
||||
|
||||
static func __get_bttv_emote_id(available_emotes:Dictionary, emote_code:String):
|
||||
return available_emotes.get(emote_code)
|
||||
|
||||
static func __get_ffz_emote_id(available_emotes:Dictionary, emote_code:String):
|
||||
return available_emotes.get(emote_code, {}).get("id")
|
||||
|
||||
func __parse_emotes(available_emotes:Dictionary, type:int)->void :
|
||||
var message:String = " " + chat_message.text + " "
|
||||
|
||||
for emote_code in available_emotes:
|
||||
var parse_emote_code:String = " " + emote_code + " "
|
||||
|
||||
if message.find(parse_emote_code) >= 0:
|
||||
emotes.append({
|
||||
"id":callv(emote_id_methods.get(type), [available_emotes, emote_code]),
|
||||
"code":emote_code,
|
||||
"type":type
|
||||
})
|
||||
|
||||
func __parse_bttv_emotes(available_emotes:Dictionary)->void :
|
||||
__parse_emotes(available_emotes, EmoteType.BTTV)
|
||||
|
||||
func __parse_ffz_emotes(available_emotes:Dictionary)->void :
|
||||
__parse_emotes(available_emotes, EmoteType.FFZ)
|
7
addons/godot-twicil/plugin.cfg
Normal file
7
addons/godot-twicil/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="godot-twicil"
|
||||
description="godot-twicil plugin"
|
||||
author="Unknown"
|
||||
version="1.0"
|
||||
script="godot-twicil-init.gd"
|
BIN
addons/godot-twicil/sprites/twicil-icon.png
Normal file
BIN
addons/godot-twicil/sprites/twicil-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 209 B |
Loading…
Add table
Add a link
Reference in a new issue