implemented image cache, fixes #1

This commit is contained in:
MennoMax 2020-03-29 19:52:55 +02:00
parent 9cc6180845
commit 2ac21a4ca3
3 changed files with 139 additions and 10 deletions

View File

@ -23,11 +23,21 @@ signal cmd_invalid_argcount(cmd_name, sender_data, cmd_data, arg_ary)
signal cmd_no_permission(cmd_name, sender_data, cmd_data, arg_ary) signal cmd_no_permission(cmd_name, sender_data, cmd_data, arg_ary)
# Twitch's ping is about to be answered with a pong. # Twitch's ping is about to be answered with a pong.
signal pong signal pong
# Emote has been downloaded
signal emote_downloaded(emote_id)
# Badge has been downloaded
signal badge_downloaded(badge_name)
# Messages starting with one of these symbols are handled. '/' will be ignored, reserved by Twitch. # Messages starting with one of these symbols are handled. '/' will be ignored, reserved by Twitch.
export(PoolStringArray) var command_prefixes : Array = ["!"] export(Array, String) var command_prefixes : Array = ["!"]
# Time to wait after each sent chat message. Values below ~0.31 will lead to a disconnect after 100 messages. # Time to wait after each sent chat message. Values below ~0.31 might lead to a disconnect after 100 messages.
export(float) var chat_timeout = 0.32 export(float) var chat_timeout = 0.32
export(bool) var get_images : bool = false
# If true, caches emotes/badges to disk, so that they don't have to be redownloaded on every restart.
# This however means that they might not be updated if they change until you clear the cache.
export(bool) var disk_cache : bool = false
# Disk Cache has to be enbaled for this to work
export(String, FILE) var disk_cache_path = "user://gift/cache"
var websocket : WebSocketClient = WebSocketClient.new() var websocket : WebSocketClient = WebSocketClient.new()
var user_regex = RegEx.new() var user_regex = RegEx.new()
@ -38,6 +48,7 @@ onready var chat_accu = chat_timeout
# Mapping of channels to their channel info, like available badges. # Mapping of channels to their channel info, like available badges.
var channels : Dictionary = {} var channels : Dictionary = {}
var commands : Dictionary = {} var commands : Dictionary = {}
var image_cache : ImageCache
# Required permission to execute the command # Required permission to execute the command
enum PermissionFlag { enum PermissionFlag {
@ -68,6 +79,9 @@ func _ready() -> void:
websocket.connect("connection_closed", self, "connection_closed") websocket.connect("connection_closed", self, "connection_closed")
websocket.connect("server_close_request", self, "sever_close_request") websocket.connect("server_close_request", self, "sever_close_request")
websocket.connect("connection_error", self, "connection_error") websocket.connect("connection_error", self, "connection_error")
if(get_images):
image_cache = ImageCache.new(disk_cache, disk_cache_path)
add_child(image_cache)
func connect_to_twitch() -> void: func connect_to_twitch() -> void:
if(websocket.connect_to_url("wss://irc-ws.chat.twitch.tv:443") != OK): if(websocket.connect_to_url("wss://irc-ws.chat.twitch.tv:443") != OK):
@ -180,7 +194,13 @@ func handle_message(message : String, tags : Dictionary) -> void:
var sender_data : SenderData = SenderData.new(user_regex.search(msg[0]).get_string(), msg[2], tags) var sender_data : SenderData = SenderData.new(user_regex.search(msg[0]).get_string(), msg[2], tags)
handle_command(sender_data, msg) handle_command(sender_data, msg)
emit_signal("chat_message", sender_data, msg[3].right(1)) emit_signal("chat_message", sender_data, msg[3].right(1))
print("TAGS: " + str(tags)) if(get_images):
if(!image_cache.badge_map.has(tags["room-id"])):
image_cache.get_badge_mappings(tags["room-id"])
for emote in tags["emotes"].split("/", false):
image_cache.get_emote(emote.split(":")[0])
for badge in tags["badges"].split(",", false):
image_cache.get_badge(badge, tags["room-id"])
"WHISPER": "WHISPER":
var sender_data : SenderData = SenderData.new(user_regex.search(msg[0]).get_string(), msg[2], tags) var sender_data : SenderData = SenderData.new(user_regex.search(msg[0]).get_string(), msg[2], tags)
handle_command(sender_data, msg, true) handle_command(sender_data, msg, true)
@ -195,6 +215,10 @@ func handle_command(sender_data : SenderData, msg : PoolStringArray, whisper : b
var command : String = msg[3].right(2) var command : String = msg[3].right(2)
var cmd_data : CommandData = commands.get(command) var cmd_data : CommandData = commands.get(command)
if(cmd_data): if(cmd_data):
if(whisper == true && cmd_data.where & WhereFlag.WHISPER != WhereFlag.WHISPER):
return
elif(whisper == false && cmd_data.where & WhereFlag.CHAT != WhereFlag.CHAT):
return
var args = "" if msg.size() < 5 else msg[4] var args = "" if msg.size() < 5 else msg[4]
var arg_ary : PoolStringArray = PoolStringArray() if args == "" else args.split(" ") var arg_ary : PoolStringArray = PoolStringArray() if args == "" else args.split(" ")
if(arg_ary.size() > cmd_data.max_args && cmd_data.max_args != -1 || arg_ary.size() < cmd_data.min_args): if(arg_ary.size() > cmd_data.max_args && cmd_data.max_args != -1 || arg_ary.size() < cmd_data.min_args):
@ -264,9 +288,3 @@ func connection_error() -> void:
func server_close_request(code : int, reason : String) -> void: func server_close_request(code : int, reason : String) -> void:
pass pass
func _enter_tree() -> void:
pass
func _exit_tree() -> void:
pass

View File

@ -3,5 +3,5 @@
name="Godot IRC For Twitch" name="Godot IRC For Twitch"
description="Godot websocket implementation for Twitch IRC." description="Godot websocket implementation for Twitch IRC."
author="MennoMax" author="MennoMax"
version="0.1.0" version="0.1.1"
script="gift.gd" script="gift.gd"

View File

@ -0,0 +1,111 @@
extends Node
class_name ImageCache
signal badge_mapping_available
var cached_images : Dictionary = {"emotes": {}, "badges": {}}
var cache_mutex = Mutex.new()
var badge_map : Dictionary = {}
var badge_mutex = Mutex.new()
var dl_queue : PoolStringArray = []
var disk_cache : bool
var disk_cache_path : String
var file : File = File.new()
var dir : Directory = Directory.new()
func _init(do_disk_cache : bool, cache_path : String) -> void:
disk_cache = do_disk_cache
disk_cache_path = cache_path
func _ready() -> void:
if(disk_cache):
for cache_dir in cached_images.keys():
cached_images[cache_dir] = {}
dir.make_dir_recursive(disk_cache_path + "/" + cache_dir)
dir.open(disk_cache_path + "/" + cache_dir)
dir.list_dir_begin(true)
var current = dir.get_next()
while current != "":
if(!dir.current_is_dir()):
file.open(dir.get_current_dir() + "/" + current, File.READ)
var img : Image = Image.new()
img.load_png_from_buffer(file.get_buffer(file.get_len()))
file.close()
var img_texture : ImageTexture = ImageTexture.new()
img_texture.create_from_image(img, 0)
cache_mutex.lock()
cached_images[cache_dir][current.get_basename()] = img_texture
cache_mutex.unlock()
current = dir.get_next()
dir.open(disk_cache_path)
dir.list_dir_begin(true)
var current = dir.get_next()
while current != "":
if(!dir.current_is_dir()):
file.open(disk_cache_path + "/" + current, File.READ)
badge_map[current.get_basename()] = parse_json(file.get_as_text())["badge_sets"]
file.close()
current = dir.get_next()
get_badge_mappings()
yield(self, "badge_mapping_available")
func create_request(url : String, resource : String, res_type : String) -> void:
var http_request = HTTPRequest.new()
http_request.connect("request_completed", self, "downloaded", [http_request, resource, res_type], CONNECT_ONESHOT)
add_child(http_request)
http_request.download_file = disk_cache_path + "/" + res_type + "/" + resource + ".png"
http_request.request(url, [], false, HTTPClient.METHOD_GET)
# Gets badge mappings for the specified channel. Empty String will get the mappings for global badges instead.
func get_badge_mappings(channel_id : String = "") -> void:
var url : String
if(channel_id == ""):
channel_id = "_global"
url = "https://badges.twitch.tv/v1/badges/global/display"
else:
url = "https://badges.twitch.tv/v1/badges/channels/" + channel_id + "/display"
if(!badge_map.has(channel_id)):
var http_request = HTTPRequest.new()
add_child(http_request)
http_request.request(url, [], false, HTTPClient.METHOD_GET)
http_request.connect("request_completed", self, "badge_mapping_received", [http_request, channel_id], CONNECT_ONESHOT)
else:
emit_signal("badge_mapping_available")
func get_emote(id : String) -> ImageTexture:
cache_mutex.lock()
if(cached_images["emotes"].has(id)):
return cached_images["emotes"][id]
else:
create_request("http://static-cdn.jtvnw.net/emoticons/v1/" + id + "/1.0", id, "emotes")
cache_mutex.unlock()
return null
func get_badge(badge_name : String, channel_id : String = "") -> ImageTexture:
cache_mutex.lock()
var badge_data : PoolStringArray = badge_name.split("/")
if(cached_images["badges"].has(badge_data[0])):
return cached_images["badges"][badge_data[0]]
var channel : String
if(!badge_map[channel_id].has(badge_data[0])):
channel_id = "_global"
create_request(badge_map[channel_id][badge_data[0]]["versions"][badge_data[1]]["image_url_1x"], badge_data[0], "badges")
cache_mutex.unlock()
return null
func downloaded(result : int, response_code : int, headers : PoolStringArray, body : PoolByteArray, request : HTTPRequest, id : String, type : String) -> void:
if(type == "emotes"):
get_parent().emit_signal("emote_downloaded", id)
elif(type == "badges"):
get_parent().emit_signal("badge_downloaded", id)
request.queue_free()
func badge_mapping_received(result : int, response_copde : int, headers : PoolStringArray, body : PoolByteArray, request : HTTPRequest, id : String) -> void:
badge_map[id] = parse_json(body.get_string_from_utf8())["badge_sets"]
if(disk_cache):
file.open(disk_cache_path + "/" + id + ".json", File.WRITE)
file.store_buffer(body)
file.close()
emit_signal("badge_mapping_available")
request.queue_free()