Add files
22
TwitchGod.tscn
Normal file
|
@ -0,0 +1,22 @@
|
|||
[gd_scene load_steps=7 format=3 uid="uid://bsved37ft3klq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/twitch_god.gd" id="1_41vp4"]
|
||||
[ext_resource type="PackedScene" uid="uid://dsev0vyt8vkuf" path="res://addons/TwitchGod/http/HttpClient.tscn" id="2_04lmt"]
|
||||
[ext_resource type="PackedScene" uid="uid://dxaclxfi6m2gk" path="res://addons/TwitchGod/http/WebsocketClient.tscn" id="2_tgrgj"]
|
||||
[ext_resource type="PackedScene" uid="uid://bn2omqaosdoqu" path="res://addons/TwitchGod/http/TwitchSetting.tscn" id="2_v0pcb"]
|
||||
[ext_resource type="PackedScene" uid="uid://d7mhkh8sua4x" path="res://addons/TwitchGod/http/HttpServer.tscn" id="4_fhjvp"]
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/auth.gd" id="6_dmql6"]
|
||||
|
||||
[node name="TwitchGod" type="Node"]
|
||||
script = ExtResource("1_41vp4")
|
||||
|
||||
[node name="TwitchSetting" parent="." instance=ExtResource("2_v0pcb")]
|
||||
|
||||
[node name="HttpServer" parent="." instance=ExtResource("4_fhjvp")]
|
||||
|
||||
[node name="WebsocketClient" parent="." instance=ExtResource("2_tgrgj")]
|
||||
|
||||
[node name="HttpClient" parent="." instance=ExtResource("2_04lmt")]
|
||||
|
||||
[node name="Auth" type="Node" parent="."]
|
||||
script = ExtResource("6_dmql6")
|
19
auth.gd
Normal file
|
@ -0,0 +1,19 @@
|
|||
extends Node
|
||||
|
||||
const BROADCASTER_ID = ""
|
||||
const USER_ID = ""
|
||||
const CLIENT_SECRET = ""
|
||||
const CLIENT_ID = ""
|
||||
const PORT = 7171
|
||||
const SCOPE = "moderator%3Amanage%3Ashoutouts+channel%3Aread%3Apolls+channel%3Amanage%3Apolls+user%3Aread%3Achat+user%3Awrite%3Achat+channel%3Aread%3Aredemptions+channel%3Amanage%3Aredemptions+channel%3Aread%3Apolls"
|
||||
const SUBS = [
|
||||
TwitchEvents.Event.CHANNEL_CHAT_MESSAGE,
|
||||
TwitchEvents.Event.CHANNEL_POLL_BEGIN,
|
||||
TwitchEvents.Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD,
|
||||
TwitchEvents.Event.CHANNEL_POLL_BEGIN,
|
||||
TwitchEvents.Event.CHANNEL_POLL_PROGRESS,
|
||||
TwitchEvents.Event.CHANNEL_POLL_END
|
||||
]
|
||||
const SCOPES = [
|
||||
""
|
||||
]
|
6
http/HttpClient.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dsev0vyt8vkuf"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/http/http_client.gd" id="1_prokr"]
|
||||
|
||||
[node name="HttpClient" type="Node"]
|
||||
script = ExtResource("1_prokr")
|
6
http/HttpServer.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://d7mhkh8sua4x"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/http/http_server.gd" id="1_im1bn"]
|
||||
|
||||
[node name="HttpServer" type="Node"]
|
||||
script = ExtResource("1_im1bn")
|
6
http/TwitchSetting.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://bn2omqaosdoqu"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/http/twitch_setting.gd" id="1_qpw3i"]
|
||||
|
||||
[node name="TwitchSetting" type="Node"]
|
||||
script = ExtResource("1_qpw3i")
|
6
http/WebsocketClient.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dxaclxfi6m2gk"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/http/websocket_client.gd" id="1_2ut2u"]
|
||||
|
||||
[node name="WebsocketClient" type="Node"]
|
||||
script = ExtResource("1_2ut2u")
|
118
http/http_client.gd
Normal file
|
@ -0,0 +1,118 @@
|
|||
extends Node
|
||||
class_name HttpClient
|
||||
|
||||
signal request_completed(type: API_TYPE, data: Dictionary)
|
||||
|
||||
const API_URL = "https://api.twitch.tv/helix"
|
||||
|
||||
enum API_TYPE {
|
||||
AUTH,
|
||||
CREATE_EVENTSUB,
|
||||
VERIFY,
|
||||
SEND_CHAT_MESSAGE,
|
||||
SHOUTOUT,
|
||||
USERS,
|
||||
EMOTE
|
||||
}
|
||||
|
||||
var auth
|
||||
|
||||
func send_message(message: String):
|
||||
var parent = get_parent()
|
||||
|
||||
request(API_TYPE.SEND_CHAT_MESSAGE, {}, {
|
||||
"broadcaster_id": parent.auth.BROADCASTER_ID,
|
||||
"sender_id": parent.auth.USER_ID,
|
||||
"message": message
|
||||
})
|
||||
|
||||
func request(type: API_TYPE, arguments: Dictionary = {}, data: Dictionary = {}):
|
||||
var path = _get_path_from_type(type, arguments)
|
||||
var headers = _get_headers_from_type(type)
|
||||
var method = HTTPClient.METHOD_POST
|
||||
|
||||
if type == API_TYPE.VERIFY:
|
||||
headers.push_back("Authorization: OAuth %s" % arguments.token)
|
||||
auth = arguments.token
|
||||
method = HTTPClient.METHOD_GET
|
||||
if type == API_TYPE.USERS:
|
||||
method = HTTPClient.METHOD_GET
|
||||
if type == API_TYPE.EMOTE:
|
||||
method = HTTPClient.METHOD_GET
|
||||
|
||||
var request = HTTPRequest.new()
|
||||
add_child(request)
|
||||
#prints(path, headers, method, JSON.stringify(data))
|
||||
if method == HTTPClient.METHOD_GET:
|
||||
request.request(path, headers, method)
|
||||
else:
|
||||
request.request(path, headers, method, JSON.stringify(data))
|
||||
request.request_completed.connect(_on_request_completed.bind(type))
|
||||
|
||||
var response = await request.request_completed
|
||||
|
||||
if response[2].has("Content-Type: image/png"):
|
||||
return response[3]
|
||||
|
||||
var string = ""
|
||||
for character in response[3]:
|
||||
string += char(character)
|
||||
|
||||
var json = JSON.parse_string(string)
|
||||
|
||||
return json
|
||||
|
||||
|
||||
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, type: API_TYPE):
|
||||
var string = ""
|
||||
for character in body:
|
||||
string += char(character)
|
||||
|
||||
if headers.has("Content-Type: application/json"):
|
||||
var json = JSON.parse_string(string)
|
||||
|
||||
request_completed.emit(type, json)
|
||||
|
||||
func _get_path_from_type(type: API_TYPE, arguments: Dictionary):
|
||||
var comp_args = ""
|
||||
|
||||
for argument in arguments:
|
||||
if comp_args == "":
|
||||
comp_args += "?%s=%s" % [argument, arguments[argument]]
|
||||
else:
|
||||
comp_args += "&%s=%s" % [argument, arguments[argument]]
|
||||
|
||||
match type:
|
||||
API_TYPE.AUTH:
|
||||
return "https://id.twitch.tv/oauth2/token" + comp_args
|
||||
API_TYPE.CREATE_EVENTSUB:
|
||||
return "%s/eventsub/subscriptions" % [API_URL]
|
||||
API_TYPE.VERIFY:
|
||||
return "https://id.twitch.tv/oauth2/validate"
|
||||
API_TYPE.SEND_CHAT_MESSAGE:
|
||||
return "%s/chat/messages" % [API_URL] + comp_args
|
||||
API_TYPE.USERS:
|
||||
return "%s/users" % [API_URL] + comp_args
|
||||
API_TYPE.SHOUTOUT:
|
||||
return "%s/chat/shoutouts" % [API_URL] + comp_args
|
||||
API_TYPE.EMOTE:
|
||||
return "https://static-cdn.jtvnw.net/emoticons/v2/%s/static/dark/1.0" % [arguments.id]
|
||||
|
||||
|
||||
func _get_headers_from_type(type: API_TYPE):
|
||||
match type:
|
||||
API_TYPE.AUTH:
|
||||
return []
|
||||
API_TYPE.VERIFY:
|
||||
return []
|
||||
_:
|
||||
return [_get_bearer(), _get_client_id(), "Content-Type: application/json"]
|
||||
|
||||
|
||||
func _get_bearer():
|
||||
return "Authorization: Bearer %s" % [auth]
|
||||
|
||||
|
||||
func _get_client_id():
|
||||
var parent = get_parent()
|
||||
return "Client-Id: %s" % [parent.auth.CLIENT_ID]
|
78
http/http_server.gd
Normal file
|
@ -0,0 +1,78 @@
|
|||
extends Node
|
||||
|
||||
var server: TCPServer
|
||||
|
||||
signal started
|
||||
signal received(auth)
|
||||
|
||||
var clients: Array[StreamPeerTCP] = []
|
||||
|
||||
var listening = false
|
||||
|
||||
func start():
|
||||
var port = get_parent().auth.PORT
|
||||
print(port)
|
||||
server = TCPServer.new()
|
||||
server.listen(port)
|
||||
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if !server: return
|
||||
if(!server.is_listening()): return;
|
||||
|
||||
if not listening:
|
||||
print("SERVER STARTED")
|
||||
started.emit()
|
||||
listening = true
|
||||
|
||||
if(server.is_connection_available()):
|
||||
_handle_connect();
|
||||
|
||||
for client in clients:
|
||||
_process_request(client);
|
||||
|
||||
|
||||
func _handle_connect():
|
||||
var client := server.take_connection()
|
||||
clients.push_back(client)
|
||||
print("CONNECT")
|
||||
|
||||
|
||||
func _process_request(client: StreamPeerTCP):
|
||||
match client.get_status():
|
||||
StreamPeerTCP.STATUS_CONNECTED:
|
||||
client.poll()
|
||||
if client.get_available_bytes() > 0:
|
||||
var string = client.get_utf8_string(client.get_available_bytes())
|
||||
var split = string.split("\n")
|
||||
var first = split[0]
|
||||
var regex = RegEx.new()
|
||||
regex.compile("code=(.*)&")
|
||||
var result = regex.search(first)
|
||||
if result:
|
||||
var auth = result.get_string(1)
|
||||
_send_response(client, "200 OK", "<html><head><title>Login</title><script>window.close()</script></head><body>Success!</body></html>".to_utf8_buffer());
|
||||
received.emit(auth)
|
||||
|
||||
func auth(arguments: Dictionary):
|
||||
var comp_args = ""
|
||||
|
||||
for argument in arguments:
|
||||
if comp_args == "":
|
||||
comp_args += "?%s=%s" % [argument, arguments[argument]]
|
||||
else:
|
||||
comp_args += "&%s=%s" % [argument, arguments[argument]]
|
||||
|
||||
OS.shell_open("https://id.twitch.tv/oauth2/authorize%s" % [comp_args])
|
||||
|
||||
|
||||
func _send_response(client, response_code : String, body : PackedByteArray) -> void:
|
||||
client.put_data(("HTTP/1.1 %s\r\n" % response_code).to_utf8_buffer())
|
||||
client.put_data("Server: Godot Engine\r\n".to_utf8_buffer())
|
||||
client.put_data(("Content-Length: %d\r\n"% body.size()).to_utf8_buffer())
|
||||
client.put_data("Connection: close\r\n".to_utf8_buffer())
|
||||
client.put_data("Content-Type: text/html; charset=UTF-8\r\n".to_utf8_buffer())
|
||||
client.put_data("\r\n".to_utf8_buffer())
|
||||
client.put_data(body)
|
||||
client.disconnect_from_host();
|
24
http/twitch_setting.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
@tool
|
||||
extends Node
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func _init():
|
||||
add_custom_project_setting("twitch_god/client_id", "Test", TYPE_STRING)
|
||||
print("Test")
|
||||
|
||||
func add_custom_project_setting(name: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string: String = ""):
|
||||
#if ProjectSettings.has_setting(name): return
|
||||
|
||||
var setting_info: Dictionary = {
|
||||
"name": name,
|
||||
"type": type,
|
||||
"hint": hint,
|
||||
"hint_string": hint_string
|
||||
}
|
||||
|
||||
ProjectSettings.set_setting(name, default_value)
|
||||
ProjectSettings.add_property_info(setting_info)
|
||||
ProjectSettings.set_initial_value(name, default_value)
|
71
http/websocket_client.gd
Normal file
|
@ -0,0 +1,71 @@
|
|||
extends Node
|
||||
class_name WebsocketClient
|
||||
|
||||
signal opened
|
||||
signal received(type: TwitchEvents.Event, data: Dictionary)
|
||||
|
||||
const URL = "wss://eventsub.wss.twitch.tv/ws"
|
||||
|
||||
var socket := WebSocketPeer.new()
|
||||
var connection_state := WebSocketPeer.STATE_CLOSED
|
||||
var id := ""
|
||||
|
||||
|
||||
# -- Built in Methods
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
socket.poll()
|
||||
|
||||
var new_connection_state := socket.get_ready_state()
|
||||
if new_connection_state != connection_state: _change_state(new_connection_state)
|
||||
|
||||
match connection_state:
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
_read_data()
|
||||
|
||||
|
||||
# -- Public Methods
|
||||
|
||||
|
||||
## Open up the websocket to start listening
|
||||
func open():
|
||||
socket.connect_to_url(URL)
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _change_state(new_state: WebSocketPeer.State):
|
||||
match new_state:
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
opened.emit()
|
||||
|
||||
connection_state = new_state
|
||||
|
||||
|
||||
func _read_data():
|
||||
while (socket.get_available_packet_count()):
|
||||
var packet := socket.get_packet()
|
||||
var data = _read_packet(packet)
|
||||
var payload = data.payload
|
||||
|
||||
match data.metadata.message_type:
|
||||
"session_welcome":
|
||||
id = payload.session.id
|
||||
"session_keepalive":
|
||||
pass
|
||||
"session_reconnect":
|
||||
pass
|
||||
_:
|
||||
var event_type = TwitchEvents.get_event_type_from_name(payload.subscription.type)
|
||||
received.emit(event_type, payload.event)
|
||||
|
||||
|
||||
func _read_packet(packet: PackedByteArray):
|
||||
var string = ""
|
||||
for chunk in packet:
|
||||
string += char(chunk)
|
||||
var data = JSON.parse_string(string)
|
||||
|
||||
return data
|
1
icons/bell.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
|
After Width: | Height: | Size: 302 B |
37
icons/bell.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dbbslha8yjaga"
|
||||
path="res://.godot/imported/bell.svg-b8e0c96cd4e3b89ad8e1d4d55e418910.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/bell.svg"
|
||||
dest_files=["res://.godot/imported/bell.svg-b8e0c96cd4e3b89ad8e1d4d55e418910.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/bot-message-square.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-message-square"><path d="M12 6V2H8"/><path d="m8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z"/><path d="M2 12h2"/><path d="M9 11v2"/><path d="M15 11v2"/><path d="M20 12h2"/></svg>
|
After Width: | Height: | Size: 391 B |
37
icons/bot-message-square.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dgxn1mb2i8ttj"
|
||||
path="res://.godot/imported/bot-message-square.svg-786cdae9f8058aaa187e28cad3dbee61.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/bot-message-square.svg"
|
||||
dest_files=["res://.godot/imported/bot-message-square.svg-786cdae9f8058aaa187e28cad3dbee61.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/cog.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cog"><path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2"/><path d="M12 22v-2"/><path d="m17 20.66-1-1.73"/><path d="M11 10.27 7 3.34"/><path d="m20.66 17-1.73-1"/><path d="m3.34 7 1.73 1"/><path d="M14 12h8"/><path d="M2 12h2"/><path d="m20.66 7-1.73 1"/><path d="m3.34 17 1.73-1"/><path d="m17 3.34-1 1.73"/><path d="m11 13.73-4 6.93"/></svg>
|
After Width: | Height: | Size: 607 B |
37
icons/cog.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dsr1447417xrq"
|
||||
path="res://.godot/imported/cog.svg-6bc9ddbf8b718bdb9823647c50ba7c4c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/cog.svg"
|
||||
dest_files=["res://.godot/imported/cog.svg-6bc9ddbf8b718bdb9823647c50ba7c4c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/key-round.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key-round"><path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 345 B |
37
icons/key-round.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c1dp2foe4qm3v"
|
||||
path="res://.godot/imported/key-round.svg-09fd537499c75dbded8a700396a22502.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/key-round.svg"
|
||||
dest_files=["res://.godot/imported/key-round.svg-09fd537499c75dbded8a700396a22502.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/message-square.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
After Width: | Height: | Size: 290 B |
37
icons/message-square.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dyg54d6jepjgh"
|
||||
path="res://.godot/imported/message-square.svg-2af3a5bb9409f01fb3cbfc5b7a3dcbd7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/message-square.svg"
|
||||
dest_files=["res://.godot/imported/message-square.svg-2af3a5bb9409f01fb3cbfc5b7a3dcbd7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/pyramid.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pyramid"><path d="M2.5 16.88a1 1 0 0 1-.32-1.43l9-13.02a1 1 0 0 1 1.64 0l9 13.01a1 1 0 0 1-.32 1.44l-8.51 4.86a2 2 0 0 1-1.98 0Z"/><path d="M12 2v20"/></svg>
|
After Width: | Height: | Size: 359 B |
37
icons/pyramid.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1738kasi15th"
|
||||
path="res://.godot/imported/pyramid.svg-b581da31e5fb1851b7edda64e9bc57e8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/pyramid.svg"
|
||||
dest_files=["res://.godot/imported/pyramid.svg-b581da31e5fb1851b7edda64e9bc57e8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/regex.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-regex"><path d="M17 3v10"/><path d="m12.67 5.5 8.66 5"/><path d="m12.67 10.5 8.66-5"/><path d="M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z"/></svg>
|
After Width: | Height: | Size: 370 B |
37
icons/regex.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dinqgmvqeen8t"
|
||||
path="res://.godot/imported/regex.svg-35497d236d1bbc6045dd332d794afc45.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/regex.svg"
|
||||
dest_files=["res://.godot/imported/regex.svg-35497d236d1bbc6045dd332d794afc45.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/server.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg>
|
After Width: | Height: | Size: 405 B |
37
icons/server.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cix7tp6kf6k44"
|
||||
path="res://.godot/imported/server.svg-b484a4103196fbad974efece4ea210c6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/server.svg"
|
||||
dest_files=["res://.godot/imported/server.svg-b484a4103196fbad974efece4ea210c6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/twitch.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-twitch"><path d="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7"/></svg>
|
After Width: | Height: | Size: 269 B |
37
icons/twitch.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cupvtgpmau5tl"
|
||||
path="res://.godot/imported/twitch.svg-caa6e01720fb38b32b0fe8194c462f21.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/twitch.svg"
|
||||
dest_files=["res://.godot/imported/twitch.svg-caa6e01720fb38b32b0fe8194c462f21.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
icons/unplug.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-unplug"><path d="m19 5 3-3"/><path d="m2 22 3-3"/><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"/><path d="M7.5 13.5 10 11"/><path d="M10.5 16.5 13 14"/><path d="m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z"/></svg>
|
After Width: | Height: | Size: 473 B |
37
icons/unplug.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://donvetfqnw6o4"
|
||||
path="res://.godot/imported/unplug.svg-36958912a145d5a5ae67d5324e3af911.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TwitchGod/icons/unplug.svg"
|
||||
dest_files=["res://.godot/imported/unplug.svg-36958912a145d5a5ae67d5324e3af911.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
12
nodes/TwitchChatCommandListener.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://cunhrde7dsed6"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_command_listener.gd" id="1_y088d"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_p4sf8"]
|
||||
|
||||
[node name="TwitchChatCommandListener" type="Node"]
|
||||
script = ExtResource("1_y088d")
|
||||
|
||||
[node name="TwitchEventListener" parent="." instance=ExtResource("2_p4sf8")]
|
||||
event = 9
|
||||
|
||||
[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"]
|
12
nodes/TwitchChatMessageListener.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://ceqp0eqllp4ai"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_message_listener.gd" id="1_8blbc"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_23mvm"]
|
||||
|
||||
[node name="TwitchChatMessageListener" type="Node"]
|
||||
script = ExtResource("1_8blbc")
|
||||
|
||||
[node name="TwitchEventListener" parent="." instance=ExtResource("2_23mvm")]
|
||||
event = 9
|
||||
|
||||
[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"]
|
12
nodes/TwitchChatRegexListener.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://bln2snjbqkg42"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_regex_listener.gd" id="1_snfls"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_lqtl8"]
|
||||
|
||||
[node name="TwitchChatRegexListener" type="Node"]
|
||||
script = ExtResource("1_snfls")
|
||||
|
||||
[node name="TwitchEventListener" parent="." instance=ExtResource("2_lqtl8")]
|
||||
event = 9
|
||||
|
||||
[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"]
|
6
nodes/TwitchEventListener.tscn
Normal file
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://b2585bqiywbwv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_event_listener.gd" id="1_siryd"]
|
||||
|
||||
[node name="TwitchEventListener" type="Node"]
|
||||
script = ExtResource("1_siryd")
|
12
nodes/TwitchPointRedemptionListener.tscn
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://vghgdn7lns0x"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_point_redemption_listener.gd" id="1_l7obv"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_86a3u"]
|
||||
|
||||
[node name="TwitchPointRedemptionListener" type="Node"]
|
||||
script = ExtResource("1_l7obv")
|
||||
|
||||
[node name="TwitchEventListener" parent="." instance=ExtResource("2_86a3u")]
|
||||
event = 36
|
||||
|
||||
[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"]
|
26
nodes/twitch_chat_command_listener.gd
Normal file
|
@ -0,0 +1,26 @@
|
|||
@icon("../icons/bot-message-square.svg")
|
||||
class_name TwitchChatCommandListener
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var names: Array[String] = []
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_twitch_event_listener_received(data):
|
||||
var message = data.message.text
|
||||
|
||||
for name in names:
|
||||
if message.begins_with("!%s " % [name]) or message == ("!%s" % [name]):
|
||||
var args = data.message.text.split(" ")
|
||||
|
||||
args.remove_at(0)
|
||||
|
||||
data.args = args
|
||||
data.argcount = args.size()
|
||||
|
||||
received.emit(data)
|
||||
return
|
17
nodes/twitch_chat_message_listener.gd
Normal file
|
@ -0,0 +1,17 @@
|
|||
@icon("../icons/message-square.svg")
|
||||
class_name TwitchChatMessageListener
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var messages: Array[String] = []
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_twitch_event_listener_received(data):
|
||||
var message = data.message.text
|
||||
|
||||
if messages.has(message):
|
||||
received.emit(data)
|
24
nodes/twitch_chat_regex_listener.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
@icon("../icons/regex.svg")
|
||||
class_name TwitchChatRegexListener
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var regexes: Array[String] = []
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_twitch_event_listener_received(data):
|
||||
var message = data.message.text
|
||||
|
||||
for regex in regexes:
|
||||
var pattern = RegEx.new()
|
||||
pattern.compile(regex)
|
||||
|
||||
var result = pattern.search(message)
|
||||
|
||||
if result:
|
||||
received.emit(data)
|
||||
return
|
24
nodes/twitch_event_listener.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
@icon("../icons/bell.svg")
|
||||
class_name TwitchEventListener
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var event: TwitchEvents.Event
|
||||
|
||||
|
||||
# -- Built in Methods
|
||||
|
||||
|
||||
func _ready():
|
||||
assert(event != null)
|
||||
|
||||
TwitchGod.websocket_client.received.connect(_on_received)
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_received(type: TwitchEvents.Event, data: Dictionary):
|
||||
if type == event:
|
||||
received.emit(data);
|
18
nodes/twitch_point_redemption_listener.gd
Normal file
|
@ -0,0 +1,18 @@
|
|||
class_name TwitchPointRedemptionListener
|
||||
extends Node
|
||||
|
||||
signal received(data: Dictionary)
|
||||
|
||||
@export var titles: Array[String] = []
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_twitch_event_listener_received(data):
|
||||
var reward_title = data.reward.title
|
||||
|
||||
print(reward_title)
|
||||
|
||||
if titles.has(reward_title):
|
||||
received.emit(data)
|
215
twitch_events.gd
Normal file
|
@ -0,0 +1,215 @@
|
|||
@tool
|
||||
class_name TwitchEvents
|
||||
extends Object
|
||||
|
||||
enum Event {
|
||||
AUTOMOD_MESSAGE_HOLD,
|
||||
AUTOMOD_MESSAGE_UPDATE,
|
||||
AUTOMOD_SETTINGS_UPDATE,
|
||||
AUTOMOD_TERMS_UPDATE,
|
||||
CHANNEL_UPDATE,
|
||||
CHANNEL_FOLLOW,
|
||||
CHANNEL_AD_BREAK_BEGIN,
|
||||
CHANNEL_CHAT_CLEAR,
|
||||
CHANNEL_CHAT_CLEAR_USER_MESSAGES,
|
||||
CHANNEL_CHAT_MESSAGE,
|
||||
CHANNEL_CHAT_MESSAGE_DELETE,
|
||||
CHANNEL_CHAT_NOTIFICATION,
|
||||
CHANNEL_CHAT_SETTINGS_UPDATE,
|
||||
CHANNEL_CHAT_USER_MESSAGE_HOLD,
|
||||
CHANNEL_CHAT_USER_MESSAGE_UPDATE,
|
||||
CHANNEL_SUBSCRIBE,
|
||||
CHANNEL_SUBSCRIPTION_END,
|
||||
CHANNEL_SUBSCRIPTION_GIFT,
|
||||
CHANNEL_SUBSCRIPTION_MESSAGE,
|
||||
CHANNEL_CHEER,
|
||||
CHANNEL_RAID,
|
||||
CHANNEL_BAN,
|
||||
CHANNEL_UNBAN,
|
||||
CHANNEL_UNBAN_REQUEST_CREATE,
|
||||
CHANNEL_UNBAN_REQUEST_RESOLVE,
|
||||
CHANNEL_MODERATE,
|
||||
CHANNEL_MODERATOR_ADD,
|
||||
CHANNEL_MODERATOR_REMOVE,
|
||||
CHANNEL_GUEST_STAR_SESSION_BEGIN,
|
||||
CHANNEL_GUEST_STAR_SESSION_END,
|
||||
CHANNEL_GUEST_STAR_GUEST_UPDATE,
|
||||
CHANNEL_GUEST_STAR_SETTINGS_UPDATE,
|
||||
CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD,
|
||||
CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD,
|
||||
CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE,
|
||||
CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE,
|
||||
CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD,
|
||||
CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE,
|
||||
CHANNEL_POLL_BEGIN,
|
||||
CHANNEL_POLL_PROGRESS,
|
||||
CHANNEL_POLL_END,
|
||||
CHANNEL_PREDICTION_BEGIN,
|
||||
CHANNEL_PREDICTION_PROGRESS,
|
||||
CHANNEL_PREDICTION_LOCK,
|
||||
CHANNEL_PREDICTION_END,
|
||||
CHANNEL_SUSPICIOUS_USER_MESSAGE,
|
||||
CHANNEL_SUSPICIOUS_USER_UPDATE,
|
||||
CHANNEL_VIP_ADD,
|
||||
CHANNEL_VIP_REMOVE,
|
||||
CHANNEL_CHARITY_CAMPAIGN_DONATE,
|
||||
CHANNEL_CHARITY_CAMPAIGN_START,
|
||||
CHANNEL_CHARITY_CAMPAIGN_PROGRESS,
|
||||
CHANNEL_CHARITY_CAMPAIGN_STOP,
|
||||
CONDUIT_SHARD_DISABLED,
|
||||
DROP_ENTITLEMENT_GRANT,
|
||||
EXTENSION_BITS_TRANSACTION_CREATE,
|
||||
CHANNEL_GOAL_BEGIN,
|
||||
CHANNEL_GOAL_PROGRESS,
|
||||
CHANNEL_GOAL_END,
|
||||
CHANNEL_HYPE_TRAIN_BEGIN,
|
||||
CHANNEL_HYPE_TRAIN_PROGRESS,
|
||||
CHANNEL_HYPE_TRAIN_END,
|
||||
CHANNEL_SHIELD_MODE_BEGIN,
|
||||
CHANNEL_SHIELD_MODE_END,
|
||||
CHANNEL_SHOUTOUT_CREATE,
|
||||
CHANNEL_SHOUTOUT_RECEIVE,
|
||||
STREAM_ONLINE,
|
||||
STREAM_OFFLINE,
|
||||
USER_AUTHORIZATION_GRANT,
|
||||
USER_AUTHORIZATION_REVOKE,
|
||||
USER_UPDATE,
|
||||
USER_WHISPER_RECEIVED
|
||||
}
|
||||
|
||||
static var _events = [
|
||||
EventInfo.new(Event.AUTOMOD_MESSAGE_HOLD, "automod.message.hold", "1"),
|
||||
EventInfo.new(Event.AUTOMOD_MESSAGE_UPDATE, "automod.message.update", "1"),
|
||||
EventInfo.new(Event.AUTOMOD_SETTINGS_UPDATE, "automod.settings.update", "1"),
|
||||
EventInfo.new(Event.AUTOMOD_TERMS_UPDATE, "automod.terms.update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_UPDATE, "channel.update", "2"),
|
||||
EventInfo.new(Event.CHANNEL_FOLLOW, "channel.follow", "2"),
|
||||
EventInfo.new(Event.CHANNEL_AD_BREAK_BEGIN, "channel.ad_break.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_CLEAR, "channel.chat.clear", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_CLEAR_USER_MESSAGES, "channel.chat.clear_user_messages", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_MESSAGE, "channel.chat.message", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_MESSAGE_DELETE, "channel.chat.message_delete", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_NOTIFICATION, "channel.chat.notification", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_SETTINGS_UPDATE, "channel.chat_settings.update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_USER_MESSAGE_HOLD, "channel.chat.user_message_hold", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHAT_USER_MESSAGE_UPDATE, "channel.chat.user_message_update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUBSCRIBE, "channel.subscribe", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUBSCRIPTION_END, "channel.subscription.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUBSCRIPTION_GIFT, "channel.subscription.gift", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUBSCRIPTION_MESSAGE, "channel.subscription.message", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHEER, "channel.cheer", "1"),
|
||||
EventInfo.new(Event.CHANNEL_RAID, "channel.raid", "1"),
|
||||
EventInfo.new(Event.CHANNEL_BAN, "channel.ban", "1"),
|
||||
EventInfo.new(Event.CHANNEL_UNBAN, "channel.unban", "1"),
|
||||
EventInfo.new(Event.CHANNEL_UNBAN_REQUEST_CREATE, "channel.unban_request.create", "1"),
|
||||
EventInfo.new(Event.CHANNEL_UNBAN_REQUEST_RESOLVE, "channel.unban_request.resolve", "1"),
|
||||
EventInfo.new(Event.CHANNEL_MODERATE, "channel.moderate", "1"),
|
||||
EventInfo.new(Event.CHANNEL_MODERATOR_ADD, "channel.moderator.add", "1"),
|
||||
EventInfo.new(Event.CHANNEL_MODERATOR_REMOVE, "channel.moderator.remove", "1"),
|
||||
EventInfo.new(Event.CHANNEL_GUEST_STAR_SESSION_BEGIN, "channel.guest_star_session.begin", "beta"),
|
||||
EventInfo.new(Event.CHANNEL_GUEST_STAR_SESSION_END, "channel.guest_star_session.end", "beta"),
|
||||
EventInfo.new(Event.CHANNEL_GUEST_STAR_GUEST_UPDATE, "channel.guest_star_guest.update", "beta"),
|
||||
EventInfo.new(Event.CHANNEL_GUEST_STAR_SETTINGS_UPDATE, "channel.guest_star_settings.update", "beta"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, "channel.channel_points_automatic_reward_redemption.add", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, "channel.channel_points_custom_reward.add", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, "channel.channel_points_custom_reward.update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, "channel.channel_points_custom_reward.remove", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, "channel.channel_points_custom_reward_redemption.add", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, "channel.channel_points_custom_reward_redemption.update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_POLL_BEGIN, "channel.poll.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_POLL_PROGRESS, "channel.poll.progress", "1"),
|
||||
EventInfo.new(Event.CHANNEL_POLL_END, "channel.poll.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_PREDICTION_BEGIN, "channel.prediction.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_PREDICTION_PROGRESS, "channel.prediction.progress", "1"),
|
||||
EventInfo.new(Event.CHANNEL_PREDICTION_LOCK, "channel.prediction.lock", "1"),
|
||||
EventInfo.new(Event.CHANNEL_PREDICTION_END, "channel.prediction.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUSPICIOUS_USER_MESSAGE, "channel.suspicious_user.message", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SUSPICIOUS_USER_UPDATE, "channel.suspicious_user.update", "1"),
|
||||
EventInfo.new(Event.CHANNEL_VIP_ADD, "channel.vip.add", "1"),
|
||||
EventInfo.new(Event.CHANNEL_VIP_REMOVE, "channel.vip.remove", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_DONATE, "channel.charity_campaign.donate", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_START, "channel.charity_campaign.start", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_PROGRESS, "channel.charity_campaign.progress", "1"),
|
||||
EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_STOP, "channel.charity_campaign.stop", "1"),
|
||||
EventInfo.new(Event.CONDUIT_SHARD_DISABLED, "conduit.shard.disabled", "1"),
|
||||
EventInfo.new(Event.DROP_ENTITLEMENT_GRANT, "drop.entitlement.grant", "1"),
|
||||
EventInfo.new(Event.EXTENSION_BITS_TRANSACTION_CREATE, "extension.bits_transaction.create", "1"),
|
||||
EventInfo.new(Event.CHANNEL_GOAL_BEGIN, "channel.goal.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_GOAL_PROGRESS, "channel.goal.progress", "1"),
|
||||
EventInfo.new(Event.CHANNEL_GOAL_END, "channel.goal.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_HYPE_TRAIN_BEGIN, "channel.hype_train.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_HYPE_TRAIN_PROGRESS, "channel.hype_train.progress", "1"),
|
||||
EventInfo.new(Event.CHANNEL_HYPE_TRAIN_END, "channel.hype_train.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SHIELD_MODE_BEGIN, "channel.shield_mode.begin", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SHIELD_MODE_END, "channel.shield_mode.end", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SHOUTOUT_CREATE, "channel.shoutout.create", "1"),
|
||||
EventInfo.new(Event.CHANNEL_SHOUTOUT_RECEIVE, "channel.shoutout.receive", "1"),
|
||||
EventInfo.new(Event.STREAM_ONLINE, "stream.online", "1"),
|
||||
EventInfo.new(Event.STREAM_OFFLINE, "stream.offline", "1"),
|
||||
EventInfo.new(Event.USER_AUTHORIZATION_GRANT, "user.authorization.grant", "1"),
|
||||
EventInfo.new(Event.USER_AUTHORIZATION_REVOKE, "user.authorization.revoke", "1"),
|
||||
EventInfo.new(Event.USER_UPDATE, "user.update", "1"),
|
||||
EventInfo.new(Event.USER_WHISPER_RECEIVED, "user.whisper.message", "1")
|
||||
]
|
||||
|
||||
|
||||
# -- Public Methods
|
||||
|
||||
|
||||
static func get_event_from_type(type: Event):
|
||||
var result = _events.filter(func(event): return event.type == type)
|
||||
|
||||
if result:
|
||||
return result[0]
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
static func get_event_from_name(name: String):
|
||||
var result = _events.filter(func(event): return event.name == name)
|
||||
|
||||
if result:
|
||||
return result[0]
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
static func get_event_type_from_name(name: String):
|
||||
var result = _events.filter(func(event): return event.name == name)
|
||||
|
||||
if result:
|
||||
return result[0].type
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
# -- Classes
|
||||
|
||||
|
||||
class EventInfo:
|
||||
var type: Event
|
||||
var name: String
|
||||
var version: String
|
||||
var conditions: Array[String]
|
||||
|
||||
|
||||
func _init(type: Event, name: String, version: String, conditions: Array[String] = []):
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.conditions = conditions
|
||||
|
||||
|
||||
func generate_sub(arguments: Dictionary):
|
||||
return {
|
||||
"type": self.name,
|
||||
"version": self.version,
|
||||
"condition": { # Change to generalize for all types
|
||||
"broadcaster_user_id": arguments.broadcaster,
|
||||
"user_id": arguments.user
|
||||
},
|
||||
"transport": {
|
||||
"method": "websocket",
|
||||
"session_id": arguments.websocket
|
||||
}
|
||||
}
|
82
twitch_god.gd
Normal file
|
@ -0,0 +1,82 @@
|
|||
extends Node
|
||||
|
||||
@onready var http_client = $"HttpClient"
|
||||
@onready var websocket_client = $"WebsocketClient"
|
||||
@onready var http_server = $"HttpServer"
|
||||
@onready var auth = $"Auth"
|
||||
|
||||
const API_URL = "https://api.twitch.tv/helix"
|
||||
var access_token = null
|
||||
|
||||
|
||||
# -- Built-in Methods
|
||||
|
||||
|
||||
func _ready():
|
||||
print("READY")
|
||||
http_server.started.connect(_on_server_started)
|
||||
http_server.received.connect(_on_server_received)
|
||||
websocket_client.opened.connect(_on_websocket_opened)
|
||||
http_client.request_completed.connect(_on_request_completed)
|
||||
|
||||
http_server.start()
|
||||
|
||||
|
||||
# -- Private Methods
|
||||
|
||||
|
||||
func _on_server_started():
|
||||
websocket_client.open()
|
||||
|
||||
|
||||
func _on_websocket_opened():
|
||||
http_server.auth(
|
||||
{
|
||||
"response_type": "code",
|
||||
"client_id": auth.CLIENT_ID,
|
||||
"redirect_uri": "http://localhost:%d" % [auth.PORT],
|
||||
"scope": auth.SCOPE
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
func _on_server_received(auth2):
|
||||
http_client.request(
|
||||
HttpClient.API_TYPE.AUTH,
|
||||
{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": auth.CLIENT_ID,
|
||||
"redirect_uri": "http://localhost:%d" % [auth.PORT],
|
||||
"code": auth2,
|
||||
"client_secret": auth.CLIENT_SECRET
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
func _on_request_completed(type: HttpClient.API_TYPE, data: Dictionary):
|
||||
match type:
|
||||
HttpClient.API_TYPE.AUTH:
|
||||
http_client.request(
|
||||
HttpClient.API_TYPE.VERIFY,
|
||||
{
|
||||
"token": data.access_token
|
||||
}
|
||||
)
|
||||
HttpClient.API_TYPE.VERIFY:
|
||||
if not websocket_client.id:
|
||||
push_error("Tried to use nonexistent websocket id. Please report to Ategon.")
|
||||
return
|
||||
|
||||
for sub in auth.SUBS:
|
||||
var event = TwitchEvents.get_event_from_type(sub)
|
||||
var sub_data = event.generate_sub({
|
||||
"broadcaster": auth.BROADCASTER_ID,
|
||||
"user": auth.USER_ID,
|
||||
"websocket": websocket_client.id
|
||||
})
|
||||
|
||||
http_client.request(
|
||||
HttpClient.API_TYPE.CREATE_EVENTSUB,
|
||||
{},
|
||||
sub_data
|
||||
)
|
12
twitch_scopes.gd
Normal file
|
@ -0,0 +1,12 @@
|
|||
@tool
|
||||
class_name TwitchScopes
|
||||
extends Object
|
||||
|
||||
enum Scope {
|
||||
ANALYTICS_READ_EXTENSIONS,
|
||||
ANALYTICS_READ_GAMES,
|
||||
BITS_READ,
|
||||
CHANNEL_MANAGE_ADS,
|
||||
CHANNEL_READ_ADS,
|
||||
CHANNEL_MANAGE_BROADCAST
|
||||
}
|