commit 48a5e71e009c0ec5052d1b58accb27036d7f1ee3 Author: Mario Steele Date: Mon Feb 23 18:38:03 2026 -0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..47091836 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png new file mode 100644 index 00000000..440997d2 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png.import new file mode 100644 index 00000000..917ba897 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgl45nhsrdtb1" +path="res://.godot/imported/Attack.png-09a9a524ef486dbd4e9fc87a75855e7b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Attack.png" +dest_files=["res://.godot/imported/Attack.png-09a9a524ef486dbd4e9fc87a75855e7b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png new file mode 100644 index 00000000..8b1629e2 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png.import new file mode 100644 index 00000000..ad07bd9b --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j42mg7bjswfj" +path="res://.godot/imported/Born.png-21714459500006bcaeecd73f1b2a8d28.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png" +dest_files=["res://.godot/imported/Born.png-21714459500006bcaeecd73f1b2a8d28.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png new file mode 100644 index 00000000..07d55c45 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png.import new file mode 100644 index 00000000..c037fd82 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7t7vdym7ran3" +path="res://.godot/imported/Death1.png-821c66ae59e5669630c9583894fba615.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png" +dest_files=["res://.godot/imported/Death1.png-821c66ae59e5669630c9583894fba615.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png new file mode 100644 index 00000000..ebb032e9 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png.import new file mode 100644 index 00000000..66efb261 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5relha3rmbp1" +path="res://.godot/imported/Death2.png-55c879f4a19756f279901bdcbae6230f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png" +dest_files=["res://.godot/imported/Death2.png-55c879f4a19756f279901bdcbae6230f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png new file mode 100644 index 00000000..8ed040a7 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png.import new file mode 100644 index 00000000..fcde7129 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cd7lcfkpafwr5" +path="res://.godot/imported/Hurt.png-eb6ea46c089a280dbab88d9fbf89ed5d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-eb6ea46c089a280dbab88d9fbf89ed5d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png new file mode 100644 index 00000000..98e03287 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png.import new file mode 100644 index 00000000..6f558dcf --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bkdchcy0gxpve" +path="res://.godot/imported/Idle.png-c1f8d6990b8b8d7300e050dae2717729.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png" +dest_files=["res://.godot/imported/Idle.png-c1f8d6990b8b8d7300e050dae2717729.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png new file mode 100644 index 00000000..44a10367 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png.import new file mode 100644 index 00000000..9755ecb7 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b674ncrflhx5f" +path="res://.godot/imported/Idle2.png-5570fef6d5c7033ce5880441f29aa381.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-5570fef6d5c7033ce5880441f29aa381.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png new file mode 100644 index 00000000..2b4cec14 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png.import new file mode 100644 index 00000000..4e90e275 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drmm6begs1ibf" +path="res://.godot/imported/Jump.png-8d3189cb20313c95a2d56c0f713c8223.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png" +dest_files=["res://.godot/imported/Jump.png-8d3189cb20313c95a2d56c0f713c8223.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png new file mode 100644 index 00000000..52f07de9 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png.import new file mode 100644 index 00000000..a0945a6c --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dopfxncw1ggsv" +path="res://.godot/imported/Sleep.png-ca6fa1012668dd27f63775516a12f58c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-ca6fa1012668dd27f63775516a12f58c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png b/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png new file mode 100644 index 00000000..6a09b9d4 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png.import b/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png.import new file mode 100644 index 00000000..dfc93dff --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hc8k4k5dnkpu" +path="res://.godot/imported/Walk.png-48eb1aba9069160a866f7b18a15a0ab4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png" +dest_files=["res://.godot/imported/Walk.png-48eb1aba9069160a866f7b18a15a0ab4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Attack.png b/ChatAvatars/CatSlimes/Assets/Black/Attack.png new file mode 100644 index 00000000..3ec0e563 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Attack.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Attack.png.import b/ChatAvatars/CatSlimes/Assets/Black/Attack.png.import new file mode 100644 index 00000000..df43bda8 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jid6qn0n16ri" +path="res://.godot/imported/Attack.png-bae2abc74f5a733ebd17e571d98093ba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Attack.png" +dest_files=["res://.godot/imported/Attack.png-bae2abc74f5a733ebd17e571d98093ba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Born.png b/ChatAvatars/CatSlimes/Assets/Black/Born.png new file mode 100644 index 00000000..af92ef8f Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Born.png.import b/ChatAvatars/CatSlimes/Assets/Black/Born.png.import new file mode 100644 index 00000000..258e6639 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0extd05k17qj" +path="res://.godot/imported/Born.png-2b49afe65a86313811b81e09a165e22e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Born.png" +dest_files=["res://.godot/imported/Born.png-2b49afe65a86313811b81e09a165e22e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Death1.png b/ChatAvatars/CatSlimes/Assets/Black/Death1.png new file mode 100644 index 00000000..e44f0bf8 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Death1.png.import b/ChatAvatars/CatSlimes/Assets/Black/Death1.png.import new file mode 100644 index 00000000..016ca4f4 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buhnl8ma73juu" +path="res://.godot/imported/Death1.png-1bd44e8b62769bc52880667b4a04e36c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Death1.png" +dest_files=["res://.godot/imported/Death1.png-1bd44e8b62769bc52880667b4a04e36c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Death2.png b/ChatAvatars/CatSlimes/Assets/Black/Death2.png new file mode 100644 index 00000000..f5ba3836 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Death2.png.import b/ChatAvatars/CatSlimes/Assets/Black/Death2.png.import new file mode 100644 index 00000000..837c7aa2 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://q3optjeppxh3" +path="res://.godot/imported/Death2.png-7c55656c95fcdd057b8fa916d44d958a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Death2.png" +dest_files=["res://.godot/imported/Death2.png-7c55656c95fcdd057b8fa916d44d958a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Hurt.png b/ChatAvatars/CatSlimes/Assets/Black/Hurt.png new file mode 100644 index 00000000..490a02c1 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/Black/Hurt.png.import new file mode 100644 index 00000000..cc6212e1 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgtr4oehvuvuv" +path="res://.godot/imported/Hurt.png-037220b57afce1790e6d767fd5da6147.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-037220b57afce1790e6d767fd5da6147.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Idle.png b/ChatAvatars/CatSlimes/Assets/Black/Idle.png new file mode 100644 index 00000000..43181b45 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Idle.png.import b/ChatAvatars/CatSlimes/Assets/Black/Idle.png.import new file mode 100644 index 00000000..61249c72 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://l4loh1kwfc4u" +path="res://.godot/imported/Idle.png-f5d80c843ddf67689ace774095be5f6b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Idle.png" +dest_files=["res://.godot/imported/Idle.png-f5d80c843ddf67689ace774095be5f6b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Idle2.png b/ChatAvatars/CatSlimes/Assets/Black/Idle2.png new file mode 100644 index 00000000..cf2b0ef2 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/Black/Idle2.png.import new file mode 100644 index 00000000..29b261e9 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6ydsvwgc8wj5" +path="res://.godot/imported/Idle2.png-bd2f24c5614c7dc5016a92735da8efc1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-bd2f24c5614c7dc5016a92735da8efc1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Jump.png b/ChatAvatars/CatSlimes/Assets/Black/Jump.png new file mode 100644 index 00000000..215a7932 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Jump.png.import b/ChatAvatars/CatSlimes/Assets/Black/Jump.png.import new file mode 100644 index 00000000..814b0096 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uvthw8438i44" +path="res://.godot/imported/Jump.png-8cf3ae44062b59139191afe6e2516d97.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Jump.png" +dest_files=["res://.godot/imported/Jump.png-8cf3ae44062b59139191afe6e2516d97.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Sleep.png b/ChatAvatars/CatSlimes/Assets/Black/Sleep.png new file mode 100644 index 00000000..557aa1d7 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/Black/Sleep.png.import new file mode 100644 index 00000000..aa573d6e --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lak5dqg5g0wi" +path="res://.godot/imported/Sleep.png-5d255b36ae94f94d3cf330fc652e2fec.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-5d255b36ae94f94d3cf330fc652e2fec.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Black/Walk.png b/ChatAvatars/CatSlimes/Assets/Black/Walk.png new file mode 100644 index 00000000..1669f250 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Black/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Black/Walk.png.import b/ChatAvatars/CatSlimes/Assets/Black/Walk.png.import new file mode 100644 index 00000000..a120cfe7 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Black/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dql3ccuo5ne5u" +path="res://.godot/imported/Walk.png-7761623da2ae14c983416041bf5c82fa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Black/Walk.png" +dest_files=["res://.godot/imported/Walk.png-7761623da2ae14c983416041bf5c82fa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Attack.png b/ChatAvatars/CatSlimes/Assets/Brown/Attack.png new file mode 100644 index 00000000..3b73c18e Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Attack.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Attack.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Attack.png.import new file mode 100644 index 00000000..daeea61f --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://df6dxik4fb57w" +path="res://.godot/imported/Attack.png-dd51900b22907da9a22d7891f8f305e6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Attack.png" +dest_files=["res://.godot/imported/Attack.png-dd51900b22907da9a22d7891f8f305e6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Born.png b/ChatAvatars/CatSlimes/Assets/Brown/Born.png new file mode 100644 index 00000000..14df706a Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Born.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Born.png.import new file mode 100644 index 00000000..fd341064 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brlgqhwjtyffo" +path="res://.godot/imported/Born.png-3ab48ded40d44877a553eefe3c9d710a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Born.png" +dest_files=["res://.godot/imported/Born.png-3ab48ded40d44877a553eefe3c9d710a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Death1.png b/ChatAvatars/CatSlimes/Assets/Brown/Death1.png new file mode 100644 index 00000000..be523c25 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Death1.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Death1.png.import new file mode 100644 index 00000000..e9def286 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwuyta22lkk4v" +path="res://.godot/imported/Death1.png-c10dd1cd92deabdc2c2b3e27c3418dc6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Death1.png" +dest_files=["res://.godot/imported/Death1.png-c10dd1cd92deabdc2c2b3e27c3418dc6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Death2.png b/ChatAvatars/CatSlimes/Assets/Brown/Death2.png new file mode 100644 index 00000000..c003154c Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Death2.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Death2.png.import new file mode 100644 index 00000000..5a6fd9aa --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2r13dd6umgct" +path="res://.godot/imported/Death2.png-528c8ed0d1fd4244c8cbea1ff407e546.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Death2.png" +dest_files=["res://.godot/imported/Death2.png-528c8ed0d1fd4244c8cbea1ff407e546.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png b/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png new file mode 100644 index 00000000..35dd8240 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png.import new file mode 100644 index 00000000..4971874b --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqw1sk1vt24nk" +path="res://.godot/imported/Hurt.png-e62377b8272c7c7de1a6392c2e992110.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-e62377b8272c7c7de1a6392c2e992110.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Idle.png b/ChatAvatars/CatSlimes/Assets/Brown/Idle.png new file mode 100644 index 00000000..78792c81 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Idle.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Idle.png.import new file mode 100644 index 00000000..cf906d91 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dh6qtjnqslkbj" +path="res://.godot/imported/Idle.png-f16048f94dcf13924eab3c3796531110.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Idle.png" +dest_files=["res://.godot/imported/Idle.png-f16048f94dcf13924eab3c3796531110.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png b/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png new file mode 100644 index 00000000..7d95939f Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png.import new file mode 100644 index 00000000..62d74569 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8wxo6x4chjoj" +path="res://.godot/imported/Idle2.png-ce7e2aeb1072e7e94b5eb01630b80f60.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-ce7e2aeb1072e7e94b5eb01630b80f60.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Jump.png b/ChatAvatars/CatSlimes/Assets/Brown/Jump.png new file mode 100644 index 00000000..74e5dbc4 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Jump.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Jump.png.import new file mode 100644 index 00000000..c345a071 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://536jlhos8rgp" +path="res://.godot/imported/Jump.png-51cd1f1cfad06ba62a09cfa1ab3c715c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Jump.png" +dest_files=["res://.godot/imported/Jump.png-51cd1f1cfad06ba62a09cfa1ab3c715c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png b/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png new file mode 100644 index 00000000..da78d6cd Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png.import new file mode 100644 index 00000000..fa164201 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0naofw2njsbc" +path="res://.godot/imported/Sleep.png-32ca5d1290ae57d89b375058f131611a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-32ca5d1290ae57d89b375058f131611a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Walk.png b/ChatAvatars/CatSlimes/Assets/Brown/Walk.png new file mode 100644 index 00000000..f9599963 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Brown/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Brown/Walk.png.import b/ChatAvatars/CatSlimes/Assets/Brown/Walk.png.import new file mode 100644 index 00000000..2f9547ec --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Brown/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ypp7rtpwdnat" +path="res://.godot/imported/Walk.png-3eca1ff71554b61ef880b09073cbdb05.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Brown/Walk.png" +dest_files=["res://.godot/imported/Walk.png-3eca1ff71554b61ef880b09073cbdb05.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png b/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png new file mode 100644 index 00000000..b597ff83 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png.import new file mode 100644 index 00000000..ce42426c --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1a4rq8bwv6r0" +path="res://.godot/imported/Attack.png-df9491c25426b87d9e6fc38c65ecc347.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Attack.png" +dest_files=["res://.godot/imported/Attack.png-df9491c25426b87d9e6fc38c65ecc347.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Born.png b/ChatAvatars/CatSlimes/Assets/Demonic/Born.png new file mode 100644 index 00000000..dfa69ddd Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Born.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Born.png.import new file mode 100644 index 00000000..9bc32cab --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bog0iftuhonm4" +path="res://.godot/imported/Born.png-65359ee32df6dd4e105344e636d7d94c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Born.png" +dest_files=["res://.godot/imported/Born.png-65359ee32df6dd4e105344e636d7d94c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png b/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png new file mode 100644 index 00000000..1850bd19 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png.import new file mode 100644 index 00000000..59a13567 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y82ghp8a7jj2" +path="res://.godot/imported/Death1.png-07891e775ee83235d73b0a65b637d165.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Death1.png" +dest_files=["res://.godot/imported/Death1.png-07891e775ee83235d73b0a65b637d165.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png b/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png new file mode 100644 index 00000000..7d04febc Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png.import new file mode 100644 index 00000000..f2c7fea9 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ci4gfjunoo5kf" +path="res://.godot/imported/Death2.png-140a49ba5a914a8d9365994f5b185209.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Death2.png" +dest_files=["res://.godot/imported/Death2.png-140a49ba5a914a8d9365994f5b185209.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png b/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png new file mode 100644 index 00000000..706cfbd8 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png.import new file mode 100644 index 00000000..346454b7 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpo0mo6j7cqcf" +path="res://.godot/imported/Hurt.png-05483c1a2a33067aa96a68cbba346eff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-05483c1a2a33067aa96a68cbba346eff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png b/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png new file mode 100644 index 00000000..875f873c Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png.import new file mode 100644 index 00000000..a72285fc --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://05gtnemuobe0" +path="res://.godot/imported/Idle.png-3dd62fffaf2dfb7f54b9e38704bf8135.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Idle.png" +dest_files=["res://.godot/imported/Idle.png-3dd62fffaf2dfb7f54b9e38704bf8135.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png b/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png new file mode 100644 index 00000000..69e75c82 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png.import new file mode 100644 index 00000000..8f06b06e --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd3pgnsmoifng" +path="res://.godot/imported/Idle2.png-bae5e77218e7be097f5b417719319e77.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-bae5e77218e7be097f5b417719319e77.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png b/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png new file mode 100644 index 00000000..2aff06eb Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png.import new file mode 100644 index 00000000..b018b738 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8qmc5xoemas" +path="res://.godot/imported/Jump.png-2efa6bbc6d60a6ca4e509006697af567.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Jump.png" +dest_files=["res://.godot/imported/Jump.png-2efa6bbc6d60a6ca4e509006697af567.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png b/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png new file mode 100644 index 00000000..61ba3538 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png.import new file mode 100644 index 00000000..ca88c81e --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnfd5jv3nn0d8" +path="res://.godot/imported/Sleep.png-316d088b774f66c49bd6307c972fc9c4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-316d088b774f66c49bd6307c972fc9c4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png b/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png new file mode 100644 index 00000000..05e203d1 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png.import b/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png.import new file mode 100644 index 00000000..bb3cf561 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Demonic/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhh8qg6y6t07n" +path="res://.godot/imported/Walk.png-ba45a67cd7a6a8a926c8447fbad891f0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Demonic/Walk.png" +dest_files=["res://.godot/imported/Walk.png-ba45a67cd7a6a8a926c8447fbad891f0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png new file mode 100644 index 00000000..606f2ecf Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png.import new file mode 100644 index 00000000..3eaf9021 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://f47kdj25e8lv" +path="res://.godot/imported/Attack.png-c9e2289cd045081bd0062af8f7521fd5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Attack.png" +dest_files=["res://.godot/imported/Attack.png-c9e2289cd045081bd0062af8f7521fd5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png new file mode 100644 index 00000000..634532cb Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png.import new file mode 100644 index 00000000..16280a25 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://du6p4jge6jdq8" +path="res://.godot/imported/Born.png-d30e984db3801a336c24243e4f95df2b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Born.png" +dest_files=["res://.godot/imported/Born.png-d30e984db3801a336c24243e4f95df2b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png new file mode 100644 index 00000000..ca3663aa Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png.import new file mode 100644 index 00000000..a857a028 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://daj0oeaikfraf" +path="res://.godot/imported/Death1.png-f8dc7f4dde29bbefbe07051fb79a7c77.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png" +dest_files=["res://.godot/imported/Death1.png-f8dc7f4dde29bbefbe07051fb79a7c77.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png new file mode 100644 index 00000000..55a70a22 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png.import new file mode 100644 index 00000000..41269e4a --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://siwis0ws4aos" +path="res://.godot/imported/Death2.png-0570c7845b1981a3f9e0454b16342bb3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png" +dest_files=["res://.godot/imported/Death2.png-0570c7845b1981a3f9e0454b16342bb3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png new file mode 100644 index 00000000..22884b17 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png.import new file mode 100644 index 00000000..d9cc711d --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://p0thdv5nvria" +path="res://.godot/imported/Hurt.png-a3f92499cf6db048180140580a3148dd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-a3f92499cf6db048180140580a3148dd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png new file mode 100644 index 00000000..711bc561 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png.import new file mode 100644 index 00000000..e71029a1 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y74swb7jjq7q" +path="res://.godot/imported/Idle.png-708f4e55e74aad7adfc7a48ea9979329.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png" +dest_files=["res://.godot/imported/Idle.png-708f4e55e74aad7adfc7a48ea9979329.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png new file mode 100644 index 00000000..e64b5d45 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png.import new file mode 100644 index 00000000..967c1685 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xka58kyerk2f" +path="res://.godot/imported/Idle2.png-66ea80d51ac72df46c0ed3edbde9c65c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-66ea80d51ac72df46c0ed3edbde9c65c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png new file mode 100644 index 00000000..fac532ec Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png.import new file mode 100644 index 00000000..d70f14b7 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c33v7bwooxpqe" +path="res://.godot/imported/Jump.png-b8e7972938449fbd5718cebdfad5fc22.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png" +dest_files=["res://.godot/imported/Jump.png-b8e7972938449fbd5718cebdfad5fc22.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png new file mode 100644 index 00000000..4f7c3145 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png.import new file mode 100644 index 00000000..91c41539 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dudj1kmmc28rb" +path="res://.godot/imported/Sleep.png-c56d53a64cd1da2ad1e772b777f8990a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-c56d53a64cd1da2ad1e772b777f8990a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png b/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png new file mode 100644 index 00000000..58e607c0 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png.import b/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png.import new file mode 100644 index 00000000..ea559457 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gqllwm1qlf30" +path="res://.godot/imported/Walk.png-932f244feb8e5acec2b14c2ca3635cb7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png" +dest_files=["res://.godot/imported/Walk.png-932f244feb8e5acec2b14c2ca3635cb7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Born.png b/ChatAvatars/CatSlimes/Assets/White/Born.png new file mode 100644 index 00000000..b63ac6ac Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Born.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Born.png.import b/ChatAvatars/CatSlimes/Assets/White/Born.png.import new file mode 100644 index 00000000..95ef7cc9 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Born.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://da34rysec8dar" +path="res://.godot/imported/Born.png-0599234163543fd97b296bdcbec5d7f5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Born.png" +dest_files=["res://.godot/imported/Born.png-0599234163543fd97b296bdcbec5d7f5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Death1.png b/ChatAvatars/CatSlimes/Assets/White/Death1.png new file mode 100644 index 00000000..16d45365 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Death1.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Death1.png.import b/ChatAvatars/CatSlimes/Assets/White/Death1.png.import new file mode 100644 index 00000000..5b5e096f --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Death1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://sf4lx2fe3662" +path="res://.godot/imported/Death1.png-f2d5bcd98ffd2a040531a75ff8e18d0f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Death1.png" +dest_files=["res://.godot/imported/Death1.png-f2d5bcd98ffd2a040531a75ff8e18d0f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Death2.png b/ChatAvatars/CatSlimes/Assets/White/Death2.png new file mode 100644 index 00000000..10d414ed Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Death2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Death2.png.import b/ChatAvatars/CatSlimes/Assets/White/Death2.png.import new file mode 100644 index 00000000..e357e7d2 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Death2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1kc2fti2n6ux" +path="res://.godot/imported/Death2.png-91fa973512ff9989706149a82d6a7c19.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Death2.png" +dest_files=["res://.godot/imported/Death2.png-91fa973512ff9989706149a82d6a7c19.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Hurt.png b/ChatAvatars/CatSlimes/Assets/White/Hurt.png new file mode 100644 index 00000000..14282eca Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Hurt.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Hurt.png.import b/ChatAvatars/CatSlimes/Assets/White/Hurt.png.import new file mode 100644 index 00000000..170c92e5 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Hurt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1237pp2s1waj" +path="res://.godot/imported/Hurt.png-dae50ecdf37e60e1e253a09243cda0cc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Hurt.png" +dest_files=["res://.godot/imported/Hurt.png-dae50ecdf37e60e1e253a09243cda0cc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Idle.png b/ChatAvatars/CatSlimes/Assets/White/Idle.png new file mode 100644 index 00000000..40951e93 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Idle.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Idle.png.import b/ChatAvatars/CatSlimes/Assets/White/Idle.png.import new file mode 100644 index 00000000..9b56bea2 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chxhs7syjxkdu" +path="res://.godot/imported/Idle.png-0c3ba0f48b3863174eef37fffdc91994.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Idle.png" +dest_files=["res://.godot/imported/Idle.png-0c3ba0f48b3863174eef37fffdc91994.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Idle2.png b/ChatAvatars/CatSlimes/Assets/White/Idle2.png new file mode 100644 index 00000000..2f00b11f Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Idle2.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Idle2.png.import b/ChatAvatars/CatSlimes/Assets/White/Idle2.png.import new file mode 100644 index 00000000..3fe7b286 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Idle2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ds68vjcnkay4j" +path="res://.godot/imported/Idle2.png-fdc60f4cd95a25e2df255ae981746936.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Idle2.png" +dest_files=["res://.godot/imported/Idle2.png-fdc60f4cd95a25e2df255ae981746936.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Jump.png b/ChatAvatars/CatSlimes/Assets/White/Jump.png new file mode 100644 index 00000000..301ca219 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Jump.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Jump.png.import b/ChatAvatars/CatSlimes/Assets/White/Jump.png.import new file mode 100644 index 00000000..198675d5 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Jump.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc202omq6x27l" +path="res://.godot/imported/Jump.png-584f002914c5f1ef2d631654acdc447c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Jump.png" +dest_files=["res://.godot/imported/Jump.png-584f002914c5f1ef2d631654acdc447c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Sleep.png b/ChatAvatars/CatSlimes/Assets/White/Sleep.png new file mode 100644 index 00000000..b66e2afd Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Sleep.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Sleep.png.import b/ChatAvatars/CatSlimes/Assets/White/Sleep.png.import new file mode 100644 index 00000000..d9e058ea --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Sleep.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ba8lkg72duvjm" +path="res://.godot/imported/Sleep.png-93fc466e1c28cf2ef62b7798129815dd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Sleep.png" +dest_files=["res://.godot/imported/Sleep.png-93fc466e1c28cf2ef62b7798129815dd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/Assets/White/Walk.png b/ChatAvatars/CatSlimes/Assets/White/Walk.png new file mode 100644 index 00000000..64f78f96 Binary files /dev/null and b/ChatAvatars/CatSlimes/Assets/White/Walk.png differ diff --git a/ChatAvatars/CatSlimes/Assets/White/Walk.png.import b/ChatAvatars/CatSlimes/Assets/White/Walk.png.import new file mode 100644 index 00000000..ef659d34 --- /dev/null +++ b/ChatAvatars/CatSlimes/Assets/White/Walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://nlw1v5by7ail" +path="res://.godot/imported/Walk.png-f58f314c31610044effc2ca36412642c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://ChatAvatars/CatSlimes/Assets/White/Walk.png" +dest_files=["res://.godot/imported/Walk.png-f58f314c31610044effc2ca36412642c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/ChatAvatars/CatSlimes/SpriteFrames/baby_blue.tres b/ChatAvatars/CatSlimes/SpriteFrames/baby_blue.tres new file mode 100644 index 00000000..b449e704 --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/baby_blue.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://c1kpmkdq2sla"] + +[ext_resource type="Texture2D" uid="uid://j42mg7bjswfj" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Born.png" id="1_3x8pd"] +[ext_resource type="Texture2D" uid="uid://bkdchcy0gxpve" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Idle.png" id="2_51tsg"] +[ext_resource type="Texture2D" uid="uid://b7t7vdym7ran3" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Death1.png" id="2_a7wvv"] +[ext_resource type="Texture2D" uid="uid://5relha3rmbp1" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Death2.png" id="2_jqe6g"] +[ext_resource type="Texture2D" uid="uid://drmm6begs1ibf" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Jump.png" id="3_jqe6g"] +[ext_resource type="Texture2D" uid="uid://dopfxncw1ggsv" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Sleep.png" id="4_a7wvv"] +[ext_resource type="Texture2D" uid="uid://b674ncrflhx5f" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Idle2.png" id="4_dte2a"] +[ext_resource type="Texture2D" uid="uid://hc8k4k5dnkpu" path="res://ChatAvatars/CatSlimes/Assets/BabyBlue/Walk.png" id="5_dte2a"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_a7wvv"] +atlas = ExtResource("1_3x8pd") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dte2a"] +atlas = ExtResource("1_3x8pd") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fr4v6"] +atlas = ExtResource("1_3x8pd") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4aggi"] +atlas = ExtResource("1_3x8pd") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2pjg4"] +atlas = ExtResource("1_3x8pd") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8h4d3"] +atlas = ExtResource("1_3x8pd") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5muuo"] +atlas = ExtResource("1_3x8pd") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dn38h"] +atlas = ExtResource("1_3x8pd") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6r671"] +atlas = ExtResource("2_a7wvv") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vep1s"] +atlas = ExtResource("2_a7wvv") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_781be"] +atlas = ExtResource("2_a7wvv") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_105jx"] +atlas = ExtResource("2_a7wvv") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7vpg1"] +atlas = ExtResource("2_a7wvv") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_r6rdf"] +atlas = ExtResource("2_a7wvv") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5l8go"] +atlas = ExtResource("2_a7wvv") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8oeyg"] +atlas = ExtResource("2_a7wvv") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_de0wl"] +atlas = ExtResource("2_a7wvv") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wfv6i"] +atlas = ExtResource("2_a7wvv") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m7yvj"] +atlas = ExtResource("2_a7wvv") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_av3q1"] +atlas = ExtResource("2_a7wvv") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_egvl3"] +atlas = ExtResource("2_jqe6g") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_pai0p"] +atlas = ExtResource("2_jqe6g") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_h482t"] +atlas = ExtResource("2_jqe6g") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0snr2"] +atlas = ExtResource("2_jqe6g") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_x0f3n"] +atlas = ExtResource("2_jqe6g") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lsnjw"] +atlas = ExtResource("2_jqe6g") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_skr01"] +atlas = ExtResource("2_jqe6g") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xyptb"] +atlas = ExtResource("2_jqe6g") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xy767"] +atlas = ExtResource("2_jqe6g") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_s5afx"] +atlas = ExtResource("2_jqe6g") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_k5q5w"] +atlas = ExtResource("2_jqe6g") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lhpkh"] +atlas = ExtResource("2_jqe6g") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_uygd1"] +atlas = ExtResource("2_jqe6g") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xtgm0"] +atlas = ExtResource("2_jqe6g") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dqljt"] +atlas = ExtResource("2_jqe6g") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_o5q2j"] +atlas = ExtResource("2_51tsg") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1i08c"] +atlas = ExtResource("2_51tsg") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m7ktd"] +atlas = ExtResource("2_51tsg") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_g5yjo"] +atlas = ExtResource("2_51tsg") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8p8jj"] +atlas = ExtResource("4_dte2a") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bxs4n"] +atlas = ExtResource("4_dte2a") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xpwa1"] +atlas = ExtResource("4_dte2a") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_khyr5"] +atlas = ExtResource("4_dte2a") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hjsem"] +atlas = ExtResource("4_dte2a") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yo64u"] +atlas = ExtResource("4_dte2a") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_flue4"] +atlas = ExtResource("4_dte2a") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1u6bg"] +atlas = ExtResource("4_dte2a") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2w85r"] +atlas = ExtResource("3_jqe6g") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_somj1"] +atlas = ExtResource("3_jqe6g") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_aih5e"] +atlas = ExtResource("3_jqe6g") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tygww"] +atlas = ExtResource("3_jqe6g") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6luu0"] +atlas = ExtResource("3_jqe6g") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_evpdh"] +atlas = ExtResource("3_jqe6g") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lscld"] +atlas = ExtResource("3_jqe6g") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ce8xe"] +atlas = ExtResource("3_jqe6g") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tebkv"] +atlas = ExtResource("3_jqe6g") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xo1b4"] +atlas = ExtResource("3_jqe6g") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_we3ps"] +atlas = ExtResource("3_jqe6g") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hqlb5"] +atlas = ExtResource("3_jqe6g") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4tlad"] +atlas = ExtResource("3_jqe6g") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_d61sa"] +atlas = ExtResource("4_a7wvv") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qlhfo"] +atlas = ExtResource("4_a7wvv") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8plxk"] +atlas = ExtResource("4_a7wvv") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_mxdrj"] +atlas = ExtResource("4_a7wvv") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gojwc"] +atlas = ExtResource("4_a7wvv") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_a2w5i"] +atlas = ExtResource("4_a7wvv") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_i2u0d"] +atlas = ExtResource("4_a7wvv") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_g7gfg"] +atlas = ExtResource("4_a7wvv") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_syorv"] +atlas = ExtResource("5_dte2a") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ayc1f"] +atlas = ExtResource("5_dte2a") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_pnwpm"] +atlas = ExtResource("5_dte2a") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6av84"] +atlas = ExtResource("5_dte2a") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_a7wvv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dte2a") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fr4v6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4aggi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2pjg4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8h4d3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5muuo") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dn38h") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_6r671") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vep1s") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_781be") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_105jx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7vpg1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_r6rdf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5l8go") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8oeyg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_de0wl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wfv6i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m7yvj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_av3q1") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_egvl3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_pai0p") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_h482t") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0snr2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_x0f3n") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lsnjw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_skr01") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xyptb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xy767") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_s5afx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_k5q5w") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lhpkh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_uygd1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xtgm0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dqljt") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_o5q2j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1i08c") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m7ktd") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_g5yjo") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_8p8jj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bxs4n") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xpwa1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_khyr5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hjsem") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yo64u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_flue4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1u6bg") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_2w85r") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_somj1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_aih5e") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tygww") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6luu0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_evpdh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lscld") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ce8xe") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tebkv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xo1b4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_we3ps") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hqlb5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4tlad") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_d61sa") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_qlhfo") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8plxk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_mxdrj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gojwc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_a2w5i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_i2u0d") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_g7gfg") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_syorv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ayc1f") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_pnwpm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6av84") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/SpriteFrames/black.tres b/ChatAvatars/CatSlimes/SpriteFrames/black.tres new file mode 100644 index 00000000..256087a9 --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/black.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://thlewdrm2n5v"] + +[ext_resource type="Texture2D" uid="uid://b0extd05k17qj" path="res://ChatAvatars/CatSlimes/Assets/Black/Born.png" id="1_vj53l"] +[ext_resource type="Texture2D" uid="uid://buhnl8ma73juu" path="res://ChatAvatars/CatSlimes/Assets/Black/Death1.png" id="2_41tmi"] +[ext_resource type="Texture2D" uid="uid://l4loh1kwfc4u" path="res://ChatAvatars/CatSlimes/Assets/Black/Idle.png" id="2_cmhjy"] +[ext_resource type="Texture2D" uid="uid://q3optjeppxh3" path="res://ChatAvatars/CatSlimes/Assets/Black/Death2.png" id="2_pjt5p"] +[ext_resource type="Texture2D" uid="uid://uvthw8438i44" path="res://ChatAvatars/CatSlimes/Assets/Black/Jump.png" id="3_pjt5p"] +[ext_resource type="Texture2D" uid="uid://lak5dqg5g0wi" path="res://ChatAvatars/CatSlimes/Assets/Black/Sleep.png" id="4_41tmi"] +[ext_resource type="Texture2D" uid="uid://b6ydsvwgc8wj5" path="res://ChatAvatars/CatSlimes/Assets/Black/Idle2.png" id="4_d0wbf"] +[ext_resource type="Texture2D" uid="uid://dql3ccuo5ne5u" path="res://ChatAvatars/CatSlimes/Assets/Black/Walk.png" id="5_d0wbf"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_41tmi"] +atlas = ExtResource("1_vj53l") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_d0wbf"] +atlas = ExtResource("1_vj53l") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nxyyi"] +atlas = ExtResource("1_vj53l") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_byger"] +atlas = ExtResource("1_vj53l") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7j6jw"] +atlas = ExtResource("1_vj53l") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lr0bl"] +atlas = ExtResource("1_vj53l") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qrrm7"] +atlas = ExtResource("1_vj53l") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_etg11"] +atlas = ExtResource("1_vj53l") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_04w36"] +atlas = ExtResource("2_41tmi") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6dt0c"] +atlas = ExtResource("2_41tmi") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3ujr2"] +atlas = ExtResource("2_41tmi") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0kwmd"] +atlas = ExtResource("2_41tmi") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lkqo7"] +atlas = ExtResource("2_41tmi") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_88wjl"] +atlas = ExtResource("2_41tmi") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ot1pp"] +atlas = ExtResource("2_41tmi") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7sa7l"] +atlas = ExtResource("2_41tmi") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wgpok"] +atlas = ExtResource("2_41tmi") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8uqh3"] +atlas = ExtResource("2_41tmi") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bpkyx"] +atlas = ExtResource("2_41tmi") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wo0gq"] +atlas = ExtResource("2_41tmi") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fip20"] +atlas = ExtResource("2_pjt5p") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8nb2b"] +atlas = ExtResource("2_pjt5p") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_04gtl"] +atlas = ExtResource("2_pjt5p") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hcpvx"] +atlas = ExtResource("2_pjt5p") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8x2nu"] +atlas = ExtResource("2_pjt5p") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l82ao"] +atlas = ExtResource("2_pjt5p") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8lw5t"] +atlas = ExtResource("2_pjt5p") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ol2nh"] +atlas = ExtResource("2_pjt5p") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dqlcm"] +atlas = ExtResource("2_pjt5p") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l5m2t"] +atlas = ExtResource("2_pjt5p") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ja45q"] +atlas = ExtResource("2_pjt5p") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6pbxd"] +atlas = ExtResource("2_pjt5p") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jnte8"] +atlas = ExtResource("2_pjt5p") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_80rpa"] +atlas = ExtResource("2_pjt5p") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jq380"] +atlas = ExtResource("2_pjt5p") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_itsn6"] +atlas = ExtResource("2_cmhjy") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_pg4b2"] +atlas = ExtResource("2_cmhjy") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_o6gl6"] +atlas = ExtResource("2_cmhjy") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fykm0"] +atlas = ExtResource("2_cmhjy") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gohdb"] +atlas = ExtResource("4_d0wbf") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bgujr"] +atlas = ExtResource("4_d0wbf") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tskml"] +atlas = ExtResource("4_d0wbf") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cqd3y"] +atlas = ExtResource("4_d0wbf") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_15t6n"] +atlas = ExtResource("4_d0wbf") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_kmcic"] +atlas = ExtResource("4_d0wbf") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ca7a5"] +atlas = ExtResource("4_d0wbf") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_a53bp"] +atlas = ExtResource("4_d0wbf") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yo8gm"] +atlas = ExtResource("3_pjt5p") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wis8a"] +atlas = ExtResource("3_pjt5p") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nxei5"] +atlas = ExtResource("3_pjt5p") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fngyb"] +atlas = ExtResource("3_pjt5p") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vo6cm"] +atlas = ExtResource("3_pjt5p") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_o0u7i"] +atlas = ExtResource("3_pjt5p") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7bkqh"] +atlas = ExtResource("3_pjt5p") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5mlsc"] +atlas = ExtResource("3_pjt5p") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1d41l"] +atlas = ExtResource("3_pjt5p") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lpde2"] +atlas = ExtResource("3_pjt5p") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7eum2"] +atlas = ExtResource("3_pjt5p") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1dhq2"] +atlas = ExtResource("3_pjt5p") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1xukk"] +atlas = ExtResource("3_pjt5p") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gt16p"] +atlas = ExtResource("4_41tmi") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4pb53"] +atlas = ExtResource("4_41tmi") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_x4j2m"] +atlas = ExtResource("4_41tmi") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_b7d6u"] +atlas = ExtResource("4_41tmi") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wiknu"] +atlas = ExtResource("4_41tmi") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_peor3"] +atlas = ExtResource("4_41tmi") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_k4pt8"] +atlas = ExtResource("4_41tmi") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1aqv2"] +atlas = ExtResource("4_41tmi") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ol2od"] +atlas = ExtResource("5_d0wbf") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4syje"] +atlas = ExtResource("5_d0wbf") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_pno04"] +atlas = ExtResource("5_d0wbf") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6fabx"] +atlas = ExtResource("5_d0wbf") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_41tmi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_d0wbf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nxyyi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_byger") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7j6jw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lr0bl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_qrrm7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_etg11") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_04w36") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6dt0c") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3ujr2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0kwmd") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lkqo7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_88wjl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ot1pp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7sa7l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wgpok") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8uqh3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bpkyx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wo0gq") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_fip20") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8nb2b") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_04gtl") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hcpvx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8x2nu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_l82ao") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8lw5t") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ol2nh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dqlcm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_l5m2t") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ja45q") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6pbxd") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jnte8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_80rpa") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jq380") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_itsn6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_pg4b2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_o6gl6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fykm0") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_gohdb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bgujr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tskml") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cqd3y") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_15t6n") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_kmcic") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ca7a5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_a53bp") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_yo8gm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wis8a") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nxei5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fngyb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vo6cm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_o0u7i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7bkqh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5mlsc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1d41l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lpde2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7eum2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1dhq2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1xukk") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_gt16p") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4pb53") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_x4j2m") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_b7d6u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wiknu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_peor3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_k4pt8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1aqv2") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_ol2od") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4syje") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_pno04") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6fabx") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/SpriteFrames/brown.tres b/ChatAvatars/CatSlimes/SpriteFrames/brown.tres new file mode 100644 index 00000000..1b683e2b --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/brown.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://bkj780l247ydf"] + +[ext_resource type="Texture2D" uid="uid://brlgqhwjtyffo" path="res://ChatAvatars/CatSlimes/Assets/Brown/Born.png" id="1_64al8"] +[ext_resource type="Texture2D" uid="uid://bwuyta22lkk4v" path="res://ChatAvatars/CatSlimes/Assets/Brown/Death1.png" id="2_4iwy2"] +[ext_resource type="Texture2D" uid="uid://c2r13dd6umgct" path="res://ChatAvatars/CatSlimes/Assets/Brown/Death2.png" id="3_slfx4"] +[ext_resource type="Texture2D" uid="uid://dh6qtjnqslkbj" path="res://ChatAvatars/CatSlimes/Assets/Brown/Idle.png" id="4_1ic3u"] +[ext_resource type="Texture2D" uid="uid://b8wxo6x4chjoj" path="res://ChatAvatars/CatSlimes/Assets/Brown/Idle2.png" id="5_7e8ol"] +[ext_resource type="Texture2D" uid="uid://536jlhos8rgp" path="res://ChatAvatars/CatSlimes/Assets/Brown/Jump.png" id="6_1knfr"] +[ext_resource type="Texture2D" uid="uid://c0naofw2njsbc" path="res://ChatAvatars/CatSlimes/Assets/Brown/Sleep.png" id="7_wgell"] +[ext_resource type="Texture2D" uid="uid://ypp7rtpwdnat" path="res://ChatAvatars/CatSlimes/Assets/Brown/Walk.png" id="8_jdged"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_h01i8"] +atlas = ExtResource("1_64al8") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2ekbq"] +atlas = ExtResource("1_64al8") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xs71s"] +atlas = ExtResource("1_64al8") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nhw58"] +atlas = ExtResource("1_64al8") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_31ky5"] +atlas = ExtResource("1_64al8") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_471f1"] +atlas = ExtResource("1_64al8") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vfxox"] +atlas = ExtResource("1_64al8") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_iwv3y"] +atlas = ExtResource("1_64al8") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fs7tt"] +atlas = ExtResource("2_4iwy2") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4dcax"] +atlas = ExtResource("2_4iwy2") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_opyyu"] +atlas = ExtResource("2_4iwy2") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8a1s1"] +atlas = ExtResource("2_4iwy2") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wbybm"] +atlas = ExtResource("2_4iwy2") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_poaut"] +atlas = ExtResource("2_4iwy2") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ivn2k"] +atlas = ExtResource("2_4iwy2") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p5lpb"] +atlas = ExtResource("2_4iwy2") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tswhh"] +atlas = ExtResource("2_4iwy2") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2f2rn"] +atlas = ExtResource("2_4iwy2") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_s2o27"] +atlas = ExtResource("2_4iwy2") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_k8m3t"] +atlas = ExtResource("2_4iwy2") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ul4ie"] +atlas = ExtResource("3_slfx4") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tr336"] +atlas = ExtResource("3_slfx4") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rr6b5"] +atlas = ExtResource("3_slfx4") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_13uq1"] +atlas = ExtResource("3_slfx4") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ac7by"] +atlas = ExtResource("3_slfx4") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3y0l5"] +atlas = ExtResource("3_slfx4") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_olh36"] +atlas = ExtResource("3_slfx4") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8duvf"] +atlas = ExtResource("3_slfx4") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_beqbb"] +atlas = ExtResource("3_slfx4") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_r0gka"] +atlas = ExtResource("3_slfx4") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_17xb6"] +atlas = ExtResource("3_slfx4") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ok27s"] +atlas = ExtResource("3_slfx4") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8rug5"] +atlas = ExtResource("3_slfx4") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p1pnu"] +atlas = ExtResource("3_slfx4") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2oriy"] +atlas = ExtResource("3_slfx4") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_uxvsw"] +atlas = ExtResource("4_1ic3u") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wseqo"] +atlas = ExtResource("4_1ic3u") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ktge5"] +atlas = ExtResource("4_1ic3u") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c6g5a"] +atlas = ExtResource("4_1ic3u") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_saogm"] +atlas = ExtResource("5_7e8ol") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8uyw3"] +atlas = ExtResource("5_7e8ol") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cp6tk"] +atlas = ExtResource("5_7e8ol") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fnyrw"] +atlas = ExtResource("5_7e8ol") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0hwle"] +atlas = ExtResource("5_7e8ol") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_q84aq"] +atlas = ExtResource("5_7e8ol") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dlfx7"] +atlas = ExtResource("5_7e8ol") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xysrh"] +atlas = ExtResource("5_7e8ol") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_n386e"] +atlas = ExtResource("6_1knfr") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gsb6k"] +atlas = ExtResource("6_1knfr") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bryfi"] +atlas = ExtResource("6_1knfr") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m6wg0"] +atlas = ExtResource("6_1knfr") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_haqwb"] +atlas = ExtResource("6_1knfr") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_chl7h"] +atlas = ExtResource("6_1knfr") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_elw6j"] +atlas = ExtResource("6_1knfr") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_pt7rf"] +atlas = ExtResource("6_1knfr") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yssmx"] +atlas = ExtResource("6_1knfr") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3q3p6"] +atlas = ExtResource("6_1knfr") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_csnqp"] +atlas = ExtResource("6_1knfr") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l58hs"] +atlas = ExtResource("6_1knfr") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2nsbs"] +atlas = ExtResource("6_1knfr") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ma76u"] +atlas = ExtResource("7_wgell") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0150j"] +atlas = ExtResource("7_wgell") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8xfnq"] +atlas = ExtResource("7_wgell") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3xbmj"] +atlas = ExtResource("7_wgell") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wn7qf"] +atlas = ExtResource("7_wgell") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ikvtt"] +atlas = ExtResource("7_wgell") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_u0gx5"] +atlas = ExtResource("7_wgell") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cj745"] +atlas = ExtResource("7_wgell") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_bvecq"] +atlas = ExtResource("8_jdged") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8y66l"] +atlas = ExtResource("8_jdged") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_252jn"] +atlas = ExtResource("8_jdged") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wlxa0"] +atlas = ExtResource("8_jdged") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_h01i8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2ekbq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xs71s") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nhw58") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_31ky5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_471f1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vfxox") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_iwv3y") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_fs7tt") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4dcax") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_opyyu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8a1s1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wbybm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_poaut") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ivn2k") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p5lpb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tswhh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2f2rn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_s2o27") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_k8m3t") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_ul4ie") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tr336") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rr6b5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_13uq1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ac7by") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3y0l5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_olh36") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8duvf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_beqbb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_r0gka") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_17xb6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ok27s") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8rug5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p1pnu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2oriy") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_uxvsw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wseqo") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ktge5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_c6g5a") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_saogm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8uyw3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cp6tk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fnyrw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0hwle") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_q84aq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dlfx7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xysrh") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_n386e") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gsb6k") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_bryfi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m6wg0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_haqwb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_chl7h") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_elw6j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_pt7rf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yssmx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3q3p6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_csnqp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_l58hs") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2nsbs") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_ma76u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0150j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8xfnq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3xbmj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wn7qf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ikvtt") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_u0gx5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cj745") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_bvecq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8y66l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_252jn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wlxa0") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/SpriteFrames/demonic.tres b/ChatAvatars/CatSlimes/SpriteFrames/demonic.tres new file mode 100644 index 00000000..7c9a595a --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/demonic.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://do48uiq7up1ie"] + +[ext_resource type="Texture2D" uid="uid://bog0iftuhonm4" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Born.png" id="1_1p0vl"] +[ext_resource type="Texture2D" uid="uid://05gtnemuobe0" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Idle.png" id="2_2ww78"] +[ext_resource type="Texture2D" uid="uid://y82ghp8a7jj2" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Death1.png" id="2_7k7bp"] +[ext_resource type="Texture2D" uid="uid://ci4gfjunoo5kf" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Death2.png" id="2_iw0fk"] +[ext_resource type="Texture2D" uid="uid://8qmc5xoemas" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Jump.png" id="3_iw0fk"] +[ext_resource type="Texture2D" uid="uid://bnfd5jv3nn0d8" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Sleep.png" id="4_7k7bp"] +[ext_resource type="Texture2D" uid="uid://dd3pgnsmoifng" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Idle2.png" id="4_hrplv"] +[ext_resource type="Texture2D" uid="uid://bhh8qg6y6t07n" path="res://ChatAvatars/CatSlimes/Assets/Demonic/Walk.png" id="5_hrplv"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_7k7bp"] +atlas = ExtResource("1_1p0vl") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hrplv"] +atlas = ExtResource("1_1p0vl") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jy6pq"] +atlas = ExtResource("1_1p0vl") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ob888"] +atlas = ExtResource("1_1p0vl") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_asxsa"] +atlas = ExtResource("1_1p0vl") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6yim0"] +atlas = ExtResource("1_1p0vl") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_66rn1"] +atlas = ExtResource("1_1p0vl") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nlpaq"] +atlas = ExtResource("1_1p0vl") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_trab6"] +atlas = ExtResource("2_7k7bp") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ru55j"] +atlas = ExtResource("2_7k7bp") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tuuw3"] +atlas = ExtResource("2_7k7bp") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xurpb"] +atlas = ExtResource("2_7k7bp") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_eqnjf"] +atlas = ExtResource("2_7k7bp") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tl4vc"] +atlas = ExtResource("2_7k7bp") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_d838l"] +atlas = ExtResource("2_7k7bp") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nro1r"] +atlas = ExtResource("2_7k7bp") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8i00j"] +atlas = ExtResource("2_7k7bp") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3gxkh"] +atlas = ExtResource("2_7k7bp") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vkim1"] +atlas = ExtResource("2_7k7bp") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_40ou3"] +atlas = ExtResource("2_7k7bp") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cixjc"] +atlas = ExtResource("2_iw0fk") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_db8mx"] +atlas = ExtResource("2_iw0fk") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3gx4w"] +atlas = ExtResource("2_iw0fk") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_eqpt0"] +atlas = ExtResource("2_iw0fk") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0dy83"] +atlas = ExtResource("2_iw0fk") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wb0q7"] +atlas = ExtResource("2_iw0fk") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_uii34"] +atlas = ExtResource("2_iw0fk") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dyq6n"] +atlas = ExtResource("2_iw0fk") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m5apr"] +atlas = ExtResource("2_iw0fk") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_exp1u"] +atlas = ExtResource("2_iw0fk") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_utny6"] +atlas = ExtResource("2_iw0fk") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0rhma"] +atlas = ExtResource("2_iw0fk") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gwvnr"] +atlas = ExtResource("2_iw0fk") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_h5omh"] +atlas = ExtResource("2_iw0fk") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ylnxn"] +atlas = ExtResource("2_iw0fk") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l76m7"] +atlas = ExtResource("2_2ww78") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tiu2u"] +atlas = ExtResource("2_2ww78") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yhddu"] +atlas = ExtResource("2_2ww78") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_teysy"] +atlas = ExtResource("2_2ww78") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7t1hv"] +atlas = ExtResource("4_hrplv") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5eqcg"] +atlas = ExtResource("4_hrplv") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5tytw"] +atlas = ExtResource("4_hrplv") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fov2u"] +atlas = ExtResource("4_hrplv") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8oxcm"] +atlas = ExtResource("4_hrplv") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ne2f6"] +atlas = ExtResource("4_hrplv") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_31ips"] +atlas = ExtResource("4_hrplv") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ifsan"] +atlas = ExtResource("4_hrplv") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1xsa2"] +atlas = ExtResource("3_iw0fk") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_x7u77"] +atlas = ExtResource("3_iw0fk") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_mngod"] +atlas = ExtResource("3_iw0fk") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8lqhg"] +atlas = ExtResource("3_iw0fk") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nawpv"] +atlas = ExtResource("3_iw0fk") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_mg417"] +atlas = ExtResource("3_iw0fk") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l0030"] +atlas = ExtResource("3_iw0fk") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_a5evk"] +atlas = ExtResource("3_iw0fk") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dmd3s"] +atlas = ExtResource("3_iw0fk") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m5eag"] +atlas = ExtResource("3_iw0fk") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e2446"] +atlas = ExtResource("3_iw0fk") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xwk48"] +atlas = ExtResource("3_iw0fk") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2c3ec"] +atlas = ExtResource("3_iw0fk") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_oqvew"] +atlas = ExtResource("4_7k7bp") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wj2k2"] +atlas = ExtResource("4_7k7bp") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_drhca"] +atlas = ExtResource("4_7k7bp") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ptc8r"] +atlas = ExtResource("4_7k7bp") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_oybcx"] +atlas = ExtResource("4_7k7bp") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_kvlej"] +atlas = ExtResource("4_7k7bp") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e03il"] +atlas = ExtResource("4_7k7bp") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5psrd"] +atlas = ExtResource("4_7k7bp") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_064be"] +atlas = ExtResource("5_hrplv") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_u4741"] +atlas = ExtResource("5_hrplv") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_fg1bn"] +atlas = ExtResource("5_hrplv") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_kp2m4"] +atlas = ExtResource("5_hrplv") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_7k7bp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hrplv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jy6pq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ob888") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_asxsa") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6yim0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_66rn1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nlpaq") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_trab6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ru55j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tuuw3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xurpb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_eqnjf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tl4vc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_d838l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nro1r") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8i00j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3gxkh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vkim1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_40ou3") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_cixjc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_db8mx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3gx4w") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_eqpt0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0dy83") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wb0q7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_uii34") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dyq6n") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m5apr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_exp1u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_utny6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0rhma") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gwvnr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_h5omh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ylnxn") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_l76m7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tiu2u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yhddu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_teysy") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_7t1hv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5eqcg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5tytw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fov2u") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8oxcm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ne2f6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_31ips") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ifsan") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_1xsa2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_x7u77") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_mngod") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8lqhg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nawpv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_mg417") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_l0030") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_a5evk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dmd3s") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m5eag") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_e2446") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xwk48") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2c3ec") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_oqvew") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wj2k2") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_drhca") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ptc8r") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_oybcx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_kvlej") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_e03il") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5psrd") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_064be") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_u4741") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_fg1bn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_kp2m4") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/SpriteFrames/rainbow.tres b/ChatAvatars/CatSlimes/SpriteFrames/rainbow.tres new file mode 100644 index 00000000..5600ea3d --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/rainbow.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://dnbaku456l8nc"] + +[ext_resource type="Texture2D" uid="uid://du6p4jge6jdq8" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Born.png" id="1_piair"] +[ext_resource type="Texture2D" uid="uid://y74swb7jjq7q" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Idle.png" id="2_mve5a"] +[ext_resource type="Texture2D" uid="uid://siwis0ws4aos" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Death2.png" id="2_oqoh7"] +[ext_resource type="Texture2D" uid="uid://daj0oeaikfraf" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Death1.png" id="2_xih17"] +[ext_resource type="Texture2D" uid="uid://c33v7bwooxpqe" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Jump.png" id="3_oqoh7"] +[ext_resource type="Texture2D" uid="uid://xka58kyerk2f" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Idle2.png" id="4_ubqwi"] +[ext_resource type="Texture2D" uid="uid://dudj1kmmc28rb" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Sleep.png" id="4_xih17"] +[ext_resource type="Texture2D" uid="uid://gqllwm1qlf30" path="res://ChatAvatars/CatSlimes/Assets/Rainbow/Walk.png" id="5_ubqwi"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_xih17"] +atlas = ExtResource("1_piair") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ubqwi"] +atlas = ExtResource("1_piair") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1tn6o"] +atlas = ExtResource("1_piair") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_th7jk"] +atlas = ExtResource("1_piair") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_u5l41"] +atlas = ExtResource("1_piair") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gvbkk"] +atlas = ExtResource("1_piair") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2ulak"] +atlas = ExtResource("1_piair") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ahxe7"] +atlas = ExtResource("1_piair") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tcwdg"] +atlas = ExtResource("2_xih17") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_h4x5p"] +atlas = ExtResource("2_xih17") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0fc2l"] +atlas = ExtResource("2_xih17") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c5yy3"] +atlas = ExtResource("2_xih17") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e7ldd"] +atlas = ExtResource("2_xih17") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_hqdrk"] +atlas = ExtResource("2_xih17") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_36lv4"] +atlas = ExtResource("2_xih17") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lbw5f"] +atlas = ExtResource("2_xih17") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_eq7jv"] +atlas = ExtResource("2_xih17") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_730ib"] +atlas = ExtResource("2_xih17") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_r8odp"] +atlas = ExtResource("2_xih17") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vc121"] +atlas = ExtResource("2_xih17") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5hvck"] +atlas = ExtResource("2_oqoh7") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5lsgc"] +atlas = ExtResource("2_oqoh7") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8tlih"] +atlas = ExtResource("2_oqoh7") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_27lmn"] +atlas = ExtResource("2_oqoh7") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_o1plj"] +atlas = ExtResource("2_oqoh7") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7y2wx"] +atlas = ExtResource("2_oqoh7") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gscr7"] +atlas = ExtResource("2_oqoh7") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_m0u01"] +atlas = ExtResource("2_oqoh7") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1k6ja"] +atlas = ExtResource("2_oqoh7") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_mnceu"] +atlas = ExtResource("2_oqoh7") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gh1wh"] +atlas = ExtResource("2_oqoh7") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_toxwo"] +atlas = ExtResource("2_oqoh7") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gvmnh"] +atlas = ExtResource("2_oqoh7") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6vclg"] +atlas = ExtResource("2_oqoh7") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0yjl5"] +atlas = ExtResource("2_oqoh7") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dnl6j"] +atlas = ExtResource("2_mve5a") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_kob8y"] +atlas = ExtResource("2_mve5a") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0rqaw"] +atlas = ExtResource("2_mve5a") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_kkg03"] +atlas = ExtResource("2_mve5a") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7fvj1"] +atlas = ExtResource("4_ubqwi") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_w2kck"] +atlas = ExtResource("4_ubqwi") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xyemo"] +atlas = ExtResource("4_ubqwi") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tlu1g"] +atlas = ExtResource("4_ubqwi") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_udf0t"] +atlas = ExtResource("4_ubqwi") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ohepd"] +atlas = ExtResource("4_ubqwi") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_o5orr"] +atlas = ExtResource("4_ubqwi") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c2yxt"] +atlas = ExtResource("4_ubqwi") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7wrgx"] +atlas = ExtResource("3_oqoh7") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lbwm0"] +atlas = ExtResource("3_oqoh7") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_r17o3"] +atlas = ExtResource("3_oqoh7") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_7da30"] +atlas = ExtResource("3_oqoh7") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_b7y1m"] +atlas = ExtResource("3_oqoh7") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yvdu5"] +atlas = ExtResource("3_oqoh7") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cr3pj"] +atlas = ExtResource("3_oqoh7") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1065s"] +atlas = ExtResource("3_oqoh7") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rikda"] +atlas = ExtResource("3_oqoh7") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_682h1"] +atlas = ExtResource("3_oqoh7") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0y6xy"] +atlas = ExtResource("3_oqoh7") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jitwf"] +atlas = ExtResource("3_oqoh7") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_estl5"] +atlas = ExtResource("3_oqoh7") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_s6k6v"] +atlas = ExtResource("4_xih17") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_uk8cy"] +atlas = ExtResource("4_xih17") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dm6be"] +atlas = ExtResource("4_xih17") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_guqio"] +atlas = ExtResource("4_xih17") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_muucv"] +atlas = ExtResource("4_xih17") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p1m6j"] +atlas = ExtResource("4_xih17") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_d7a7l"] +atlas = ExtResource("4_xih17") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rqf2l"] +atlas = ExtResource("4_xih17") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ii8i0"] +atlas = ExtResource("5_ubqwi") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ajw6f"] +atlas = ExtResource("5_ubqwi") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_noj1m"] +atlas = ExtResource("5_ubqwi") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6xfhm"] +atlas = ExtResource("5_ubqwi") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_xih17") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ubqwi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1tn6o") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_th7jk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_u5l41") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gvbkk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2ulak") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ahxe7") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_tcwdg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_h4x5p") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0fc2l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_c5yy3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_e7ldd") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_hqdrk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_36lv4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lbw5f") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_eq7jv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_730ib") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_r8odp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vc121") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_5hvck") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5lsgc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8tlih") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_27lmn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_o1plj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7y2wx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gscr7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_m0u01") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1k6ja") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_mnceu") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gh1wh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_toxwo") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gvmnh") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6vclg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0yjl5") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_dnl6j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_kob8y") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0rqaw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_kkg03") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_7fvj1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_w2kck") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xyemo") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_tlu1g") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_udf0t") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ohepd") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_o5orr") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_c2yxt") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_7wrgx") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lbwm0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_r17o3") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_7da30") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_b7y1m") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yvdu5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cr3pj") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1065s") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rikda") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_682h1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0y6xy") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jitwf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_estl5") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_s6k6v") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_uk8cy") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dm6be") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_guqio") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_muucv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p1m6j") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_d7a7l") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rqf2l") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_ii8i0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ajw6f") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_noj1m") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6xfhm") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/SpriteFrames/white.tres b/ChatAvatars/CatSlimes/SpriteFrames/white.tres new file mode 100644 index 00000000..e5c40367 --- /dev/null +++ b/ChatAvatars/CatSlimes/SpriteFrames/white.tres @@ -0,0 +1,557 @@ +[gd_resource type="SpriteFrames" format=3 uid="uid://brmqggg5n6xv0"] + +[ext_resource type="Texture2D" uid="uid://da34rysec8dar" path="res://ChatAvatars/CatSlimes/Assets/White/Born.png" id="1_mnpu8"] +[ext_resource type="Texture2D" uid="uid://sf4lx2fe3662" path="res://ChatAvatars/CatSlimes/Assets/White/Death1.png" id="2_hddei"] +[ext_resource type="Texture2D" uid="uid://d1kc2fti2n6ux" path="res://ChatAvatars/CatSlimes/Assets/White/Death2.png" id="3_ogd38"] +[ext_resource type="Texture2D" uid="uid://chxhs7syjxkdu" path="res://ChatAvatars/CatSlimes/Assets/White/Idle.png" id="4_yellk"] +[ext_resource type="Texture2D" uid="uid://ds68vjcnkay4j" path="res://ChatAvatars/CatSlimes/Assets/White/Idle2.png" id="5_8h1i7"] +[ext_resource type="Texture2D" uid="uid://bc202omq6x27l" path="res://ChatAvatars/CatSlimes/Assets/White/Jump.png" id="6_a33o2"] +[ext_resource type="Texture2D" uid="uid://ba8lkg72duvjm" path="res://ChatAvatars/CatSlimes/Assets/White/Sleep.png" id="7_vc2ls"] +[ext_resource type="Texture2D" uid="uid://nlw1v5by7ail" path="res://ChatAvatars/CatSlimes/Assets/White/Walk.png" id="8_wfqj8"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_i2v17"] +atlas = ExtResource("1_mnpu8") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e8hg4"] +atlas = ExtResource("1_mnpu8") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vct6k"] +atlas = ExtResource("1_mnpu8") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_y8yfq"] +atlas = ExtResource("1_mnpu8") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_w0vlv"] +atlas = ExtResource("1_mnpu8") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jem82"] +atlas = ExtResource("1_mnpu8") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_8qnb6"] +atlas = ExtResource("1_mnpu8") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ibla5"] +atlas = ExtResource("1_mnpu8") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c1bip"] +atlas = ExtResource("2_hddei") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rx0kg"] +atlas = ExtResource("2_hddei") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_keg6y"] +atlas = ExtResource("2_hddei") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_wuyly"] +atlas = ExtResource("2_hddei") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_3xim5"] +atlas = ExtResource("2_hddei") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c21x8"] +atlas = ExtResource("2_hddei") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ssddq"] +atlas = ExtResource("2_hddei") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_n8jaa"] +atlas = ExtResource("2_hddei") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dtta7"] +atlas = ExtResource("2_hddei") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ssxrp"] +atlas = ExtResource("2_hddei") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_sj7s4"] +atlas = ExtResource("2_hddei") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_xy8ow"] +atlas = ExtResource("2_hddei") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_efh2p"] +atlas = ExtResource("3_ogd38") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_opaub"] +atlas = ExtResource("3_ogd38") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ot6e0"] +atlas = ExtResource("3_ogd38") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_376xn"] +atlas = ExtResource("3_ogd38") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6dkwn"] +atlas = ExtResource("3_ogd38") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_73be4"] +atlas = ExtResource("3_ogd38") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vjkdf"] +atlas = ExtResource("3_ogd38") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ocs7o"] +atlas = ExtResource("3_ogd38") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_74n1k"] +atlas = ExtResource("3_ogd38") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4aqhy"] +atlas = ExtResource("3_ogd38") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_j4k0o"] +atlas = ExtResource("3_ogd38") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l8lcn"] +atlas = ExtResource("3_ogd38") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_e87fi"] +atlas = ExtResource("3_ogd38") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2chv8"] +atlas = ExtResource("3_ogd38") +region = Rect2(416, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gjtrg"] +atlas = ExtResource("3_ogd38") +region = Rect2(448, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1wffb"] +atlas = ExtResource("4_yellk") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_ahte1"] +atlas = ExtResource("4_yellk") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_gegu8"] +atlas = ExtResource("4_yellk") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5uy7t"] +atlas = ExtResource("4_yellk") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6yhau"] +atlas = ExtResource("5_8h1i7") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_2mia7"] +atlas = ExtResource("5_8h1i7") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_yfxar"] +atlas = ExtResource("5_8h1i7") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_et3q7"] +atlas = ExtResource("5_8h1i7") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_18h7x"] +atlas = ExtResource("5_8h1i7") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4tb2o"] +atlas = ExtResource("5_8h1i7") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_dojyi"] +atlas = ExtResource("5_8h1i7") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1wuii"] +atlas = ExtResource("5_8h1i7") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_l310n"] +atlas = ExtResource("6_a33o2") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p0edc"] +atlas = ExtResource("6_a33o2") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cg1tk"] +atlas = ExtResource("6_a33o2") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_f6r4h"] +atlas = ExtResource("6_a33o2") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0x25a"] +atlas = ExtResource("6_a33o2") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_vufn6"] +atlas = ExtResource("6_a33o2") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_nst8i"] +atlas = ExtResource("6_a33o2") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6jcav"] +atlas = ExtResource("6_a33o2") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_cmyps"] +atlas = ExtResource("6_a33o2") +region = Rect2(256, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_5e8xi"] +atlas = ExtResource("6_a33o2") +region = Rect2(288, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_51873"] +atlas = ExtResource("6_a33o2") +region = Rect2(320, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_q3j12"] +atlas = ExtResource("6_a33o2") +region = Rect2(352, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_g7jlo"] +atlas = ExtResource("6_a33o2") +region = Rect2(384, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_jbfmy"] +atlas = ExtResource("7_vc2ls") +region = Rect2(0, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_uxtx4"] +atlas = ExtResource("7_vc2ls") +region = Rect2(64, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_24jjm"] +atlas = ExtResource("7_vc2ls") +region = Rect2(128, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qw1pe"] +atlas = ExtResource("7_vc2ls") +region = Rect2(192, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_lfjns"] +atlas = ExtResource("7_vc2ls") +region = Rect2(256, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_rrlgw"] +atlas = ExtResource("7_vc2ls") +region = Rect2(320, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_p6aha"] +atlas = ExtResource("7_vc2ls") +region = Rect2(384, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_6pg1q"] +atlas = ExtResource("7_vc2ls") +region = Rect2(448, 0, 64, 64) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1osqm"] +atlas = ExtResource("8_wfqj8") +region = Rect2(0, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_af58x"] +atlas = ExtResource("8_wfqj8") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_4ya2m"] +atlas = ExtResource("8_wfqj8") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_htisy"] +atlas = ExtResource("8_wfqj8") +region = Rect2(96, 0, 32, 32) + +[resource] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_i2v17") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_e8hg4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vct6k") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_y8yfq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_w0vlv") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_jem82") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_8qnb6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ibla5") +}], +"loop": false, +"name": &"born", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_c1bip") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rx0kg") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_keg6y") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_wuyly") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_3xim5") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_c21x8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ssddq") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_n8jaa") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dtta7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ssxrp") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_sj7s4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_xy8ow") +}], +"loop": false, +"name": &"death1", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_efh2p") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_opaub") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ot6e0") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_376xn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6dkwn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_73be4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vjkdf") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ocs7o") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_74n1k") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4aqhy") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_j4k0o") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_l8lcn") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_e87fi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2chv8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gjtrg") +}], +"loop": false, +"name": &"death2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_1wffb") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_ahte1") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_gegu8") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5uy7t") +}], +"loop": true, +"name": &"idle", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_6yhau") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_2mia7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_yfxar") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_et3q7") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_18h7x") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4tb2o") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_dojyi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_1wuii") +}], +"loop": true, +"name": &"idle2", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_l310n") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p0edc") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cg1tk") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_f6r4h") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_0x25a") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_vufn6") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_nst8i") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6jcav") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_cmyps") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_5e8xi") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_51873") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_q3j12") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_g7jlo") +}], +"loop": false, +"name": &"jump", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_jbfmy") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_uxtx4") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_24jjm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_qw1pe") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_lfjns") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_rrlgw") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_p6aha") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_6pg1q") +}], +"loop": true, +"name": &"sleep", +"speed": 5.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": SubResource("AtlasTexture_1osqm") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_af58x") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_4ya2m") +}, { +"duration": 1.0, +"texture": SubResource("AtlasTexture_htisy") +}], +"loop": true, +"name": &"walk", +"speed": 5.0 +}] diff --git a/ChatAvatars/CatSlimes/avatar_states/born.gd b/ChatAvatars/CatSlimes/avatar_states/born.gd new file mode 100644 index 00000000..ca40327e --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/born.gd @@ -0,0 +1,19 @@ +extends ChatterState +class_name BornState + +func _enter() -> void: + while not chatter.visual: + await get_tree().process_frame + print("We have visual") + chatter.is_gravity_disabled = true + chatter.visual.play(&"born") + await chatter.visual.animation_finished + print("Animation Finished") + chatter.is_gravity_disabled = false + +func _exit() -> void: + pass + +func _update(_delta: float) -> void: + if chatter.is_on_floor(): + transitioned.emit(self, "idle") diff --git a/ChatAvatars/CatSlimes/avatar_states/born.gd.uid b/ChatAvatars/CatSlimes/avatar_states/born.gd.uid new file mode 100644 index 00000000..73dcaabd --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/born.gd.uid @@ -0,0 +1 @@ +uid://ce5u2o0ht5wxi diff --git a/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd b/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd new file mode 100644 index 00000000..32be00d4 --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd @@ -0,0 +1,4 @@ +extends State +class_name ChatterState + +@export var chatter: CatSlimeAvatar diff --git a/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd.uid b/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd.uid new file mode 100644 index 00000000..7c9b1f6a --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/chatter_state.gd.uid @@ -0,0 +1 @@ +uid://ckopogfaxn5ax diff --git a/ChatAvatars/CatSlimes/avatar_states/idle.gd b/ChatAvatars/CatSlimes/avatar_states/idle.gd new file mode 100644 index 00000000..d154522d --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/idle.gd @@ -0,0 +1,18 @@ +extends ChatterState +class_name IdleState + +var idle_time: float = 3.0 + +func _enter() -> void: + chatter.visual.play(&"idle") + chatter.velocity = Vector2.ZERO + idle_time = randf_range(5,7) + +func _update(delta: float) -> void: + if idle_time > 0: + idle_time -= delta + else: + if randf() > 0.5: + transitioned.emit(self, "move") + else: + idle_time = randf_range(5,7) diff --git a/ChatAvatars/CatSlimes/avatar_states/idle.gd.uid b/ChatAvatars/CatSlimes/avatar_states/idle.gd.uid new file mode 100644 index 00000000..9e339e25 --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/idle.gd.uid @@ -0,0 +1 @@ +uid://c356g7uw5nbg5 diff --git a/ChatAvatars/CatSlimes/avatar_states/move.gd b/ChatAvatars/CatSlimes/avatar_states/move.gd new file mode 100644 index 00000000..eeb3af06 --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/move.gd @@ -0,0 +1,32 @@ +extends ChatterState +class_name MoveState + +@export var move_speed := 60.0 + +var move_direction: Vector2 = Vector2.ZERO +var wander_time: float = 3.0 + +func randomize_wander(): + move_direction = Vector2(randf_range(-1,1), 0).normalized() + wander_time = randf_range(1,6) + +func _enter() -> void: + randomize_wander() + +func _exit() -> void: + pass + +func _update(delta: float) -> void: + if wander_time > 0: + wander_time -= delta + else: + if randf() > 0.5: + transitioned.emit(self, "idle") + else: + randomize_wander() + +func _physics_update(_delta: float) -> void: + if chatter: + if chatter.is_on_wall(): + move_direction = -move_direction + chatter.velocity = move_direction * move_speed diff --git a/ChatAvatars/CatSlimes/avatar_states/move.gd.uid b/ChatAvatars/CatSlimes/avatar_states/move.gd.uid new file mode 100644 index 00000000..2373b55e --- /dev/null +++ b/ChatAvatars/CatSlimes/avatar_states/move.gd.uid @@ -0,0 +1 @@ +uid://df4p557wb3wl4 diff --git a/ChatAvatars/CatSlimes/cat_world.gd b/ChatAvatars/CatSlimes/cat_world.gd new file mode 100644 index 00000000..a142ac70 --- /dev/null +++ b/ChatAvatars/CatSlimes/cat_world.gd @@ -0,0 +1,83 @@ +@tool +extends Node2D + +static var ChatAvatarScene: PackedScene = load("res://ChatAvatars/CatSlimes/chat_avatar.tscn") +var _log: TwitchLogger = TwitchLogger.new("CatWorldAvatars") +@onready var chat_avatar_commands: Node = %ChatAvatarCommands +@onready var avatars: Node2D = %Avatars +@onready var floor_point: StaticBody2D = %Floor +@onready var wall_left: StaticBody2D = %WallLeft +@onready var wall_right: StaticBody2D = %WallRight + +var floor_level: float = 0.0 : + set(value): + floor_level = value + _handle_resize_event() + +static var AvatarSkins: Dictionary[String, SpriteFrames] = { + "blue": load("res://ChatAvatars/CatSlimes/SpriteFrames/baby_blue.tres"), + "black": load("res://ChatAvatars/CatSlimes/SpriteFrames/black.tres"), + "brown": load("res://ChatAvatars/CatSlimes/SpriteFrames/brown.tres"), + "demonic": load("res://ChatAvatars/CatSlimes/SpriteFrames/demonic.tres"), + "rainbow": load("res://ChatAvatars/CatSlimes/SpriteFrames/rainbow.tres"), + "white": load("res://ChatAvatars/CatSlimes/SpriteFrames/white.tres"), +} + +var _chatters: Dictionary[String, AvatarInfo] = {} + +func _ready() -> void: + if Engine.is_editor_hint(): return + while not Globals.twitcher: + await get_tree().process_frame + + ChatManager.first_chat.connect(_handle_first_chat) + Globals.twitcher.add_command("avatar", _handle_command_avatar, 1, 1) + get_viewport().get_window().size_changed.connect(_handle_resize_event) + +func _handle_resize_event() -> void: + var rect := get_viewport_rect() + for avatar: Node2D in avatars.get_children(): + avatar.position.y = rect.size.y if floor_level == 0.0 else floor_level + floor_point.position.y = rect.size.y if floor_level == 0.0 else floor_level + wall_right.position.x = rect.size.x + +func _handle_first_chat(user: TwitchUser, chatter: Chatter, _msg: TwitchChatMessage) -> void: + if _chatters.has(user.id): return + _log.d("New First Chatter: %s(%s)" % [user.display_name, user.id] ) + var slime: CatSlimeAvatar = ChatAvatarScene.instantiate() + slime.name = user.id + slime.avatar_name = user.display_name + if chatter.extra_data.has("cat_world_avatar"): + _log.d("Chatter has Skin: %s" % [chatter.extra_data.cat_world_avatar]) + slime.skin = AvatarSkins[chatter.extra_data.cat_world_avatar] + else: + var skin: String = AvatarSkins.keys().pick_random() + _log.d("Random Skin: %s" % [skin]) + slime.skin = AvatarSkins[skin] + slime.position = Vector2(randf_range(32, 1120), 301) + var ai: AvatarInfo = AvatarInfo.new() + ai.user = user + ai.chatter = chatter + ai.slime = slime + _chatters[user.id] = ai + avatars.add_child(slime) + +func _handle_command_avatar(_from_username: String, info: TwitchCommandInfo, args: PackedStringArray) -> void: + var msg: TwitchChatMessage = info.original_message + var uid: String = msg.chatter_user_id + if not _chatters.has(uid): + Globals.twitcher.reply_message("No avatar found!", msg.message_id) + return + var ai: AvatarInfo = _chatters[uid] + if args[0].to_lower() in AvatarSkins.keys(): + ai.chatter.extra_data["cat_world_avatar"] = args[0].to_lower() + ai.chatter.save() + ai.slime.skin = AvatarSkins[args[0].to_lower()] + Globals.twitcher.reply_message("Changed avatar skin to %s" % args[0].to_lower(), msg.message_id) + else: + Globals.twitcher.reply_message("Unknown skin '%s', valid options are: %s" % [args[0].to_lower(), ", ".join(AvatarSkins.keys())], msg.message_id) + +class AvatarInfo: + var user: TwitchUser + var chatter: Chatter + var slime: CatSlimeAvatar diff --git a/ChatAvatars/CatSlimes/cat_world.gd.uid b/ChatAvatars/CatSlimes/cat_world.gd.uid new file mode 100644 index 00000000..d2ba514b --- /dev/null +++ b/ChatAvatars/CatSlimes/cat_world.gd.uid @@ -0,0 +1 @@ +uid://p1cehbl26s6s diff --git a/ChatAvatars/CatSlimes/cat_world.tscn b/ChatAvatars/CatSlimes/cat_world.tscn new file mode 100644 index 00000000..9583967e --- /dev/null +++ b/ChatAvatars/CatSlimes/cat_world.tscn @@ -0,0 +1,45 @@ +[gd_scene format=3 uid="uid://djdxpwrmlrsp8"] + +[ext_resource type="Script" uid="uid://p1cehbl26s6s" path="res://ChatAvatars/CatSlimes/cat_world.gd" id="1_dk8qt"] +[ext_resource type="Script" uid="uid://dlnv6wo7uxlks" path="res://ChatAvatars/CatSlimes/chat_avatar_commands.gd" id="2_dk8qt"] + +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_nv0qy"] + +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_xf7jw"] +normal = Vector2(1, 0) + +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_6ii2h"] +normal = Vector2(-1, 0) + +[node name="CatWorld" type="Node2D" unique_id=1301842669] +script = ExtResource("1_dk8qt") + +[node name="WorldBoundries" type="Node2D" parent="." unique_id=1127747425] + +[node name="Floor" type="StaticBody2D" parent="WorldBoundries" unique_id=746537539] +unique_name_in_owner = true +position = Vector2(0, 648) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="WorldBoundries/Floor" unique_id=1495679580] +shape = SubResource("WorldBoundaryShape2D_nv0qy") + +[node name="WallLeft" type="StaticBody2D" parent="WorldBoundries" unique_id=29767851] +unique_name_in_owner = true + +[node name="CollisionShape2D" type="CollisionShape2D" parent="WorldBoundries/WallLeft" unique_id=1050813646] +shape = SubResource("WorldBoundaryShape2D_xf7jw") + +[node name="WallRight" type="StaticBody2D" parent="WorldBoundries" unique_id=662355024] +unique_name_in_owner = true +position = Vector2(1152, 0) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="WorldBoundries/WallRight" unique_id=1255895407] +shape = SubResource("WorldBoundaryShape2D_6ii2h") + +[node name="ChatAvatarCommands" type="Node" parent="." unique_id=361194247 node_paths=PackedStringArray("avatars")] +unique_name_in_owner = true +script = ExtResource("2_dk8qt") +avatars = NodePath("../Avatars") + +[node name="Avatars" type="Node2D" parent="." unique_id=2073654203] +unique_name_in_owner = true diff --git a/ChatAvatars/CatSlimes/chat_avatar.gd b/ChatAvatars/CatSlimes/chat_avatar.gd new file mode 100644 index 00000000..53c577e7 --- /dev/null +++ b/ChatAvatars/CatSlimes/chat_avatar.gd @@ -0,0 +1,93 @@ +@tool +extends CharacterBody2D +class_name CatSlimeAvatar + +@export var skin: SpriteFrames: + set(value): + skin = value + if visual: + visual.sprite_frames = skin + +@export var avatar_name: String = "Twitch User": + set(value): + avatar_name = value + if display_name: + display_name.text = value + _resize_collision() + +@onready var visual: AnimatedSprite2D = %Visual +@onready var display_name: Label = %DisplayName +@onready var state_machine: StateMachine = %StateMachine +@onready var overlap: Area2D = %Overlap +@onready var overlap_collision: CollisionShape2D = %OverlapCollision + +var is_gravity_disabled: bool = false +var _overlapping: Array[Node] = [] +var _hide_tween: Tween +var _show_tween: Tween + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + visual.animation_changed.connect(_handle_animation_changed) + overlap.area_entered.connect(_handle_overlap_entered) + overlap.area_exited.connect(_handle_overlap_exited) + + visual.sprite_frames = skin + display_name.text = avatar_name + _resize_collision() + +func _handle_animation_changed() -> void: + if visual.animation == &"sleep": + visual.offset = Vector2(16,-16) + else: + visual.offset = Vector2.ZERO + +func _handle_overlap_entered(node: Node) -> void: + _overlapping.append(node) + +func _handle_overlap_exited(node: Node) -> void: + _overlapping.erase(node) + +func _process(_delta: float) -> void: + if _overlapping.size() > 0: # Overlapping + if _hide_tween: return # Tween in progress + if display_name.modulate.a == 0.0: return # Display name is already hidden. + if _show_tween: # Show Tween is running, stop it, and clear it out. + _show_tween.kill() + _show_tween = null + _hide_tween = create_tween() + _hide_tween.tween_property(display_name, "modulate:a", 0.0, 0.5) + _hide_tween.tween_callback(func(): _hide_tween = null) + else: # Not Overlapping + if _show_tween: return # Tween in progress + if display_name.modulate.a == 1.0: return # Display name is shown. + if _hide_tween: # Hide Tween is running, stop it, and clear it out. + _hide_tween.kill() + _hide_tween = null + _show_tween = create_tween() + _show_tween.tween_property(display_name, "modulate:a", 1.0, 0.5) + _show_tween.tween_callback(func(): _show_tween = null) + pass + + +func _physics_process(delta: float) -> void: + if Engine.is_editor_hint(): + return + if is_gravity_disabled: + velocity.y = 0 + else: + velocity.y += get_gravity().y * delta + move_and_slide() + + if state_machine.current_state.name.to_lower() != "born": + if velocity.length() > 0 and visual.animation != &"walk": + visual.play(&"walk") + elif velocity.length() == 0 and visual.animation != &"idle": + visual.play(&"idle") + + visual.flip_h = velocity.x < 0 + +func _resize_collision() -> void: + await get_tree().process_frame + var lsize = display_name.size + overlap_collision.shape.size.x = lsize.x diff --git a/ChatAvatars/CatSlimes/chat_avatar.gd.uid b/ChatAvatars/CatSlimes/chat_avatar.gd.uid new file mode 100644 index 00000000..444965ce --- /dev/null +++ b/ChatAvatars/CatSlimes/chat_avatar.gd.uid @@ -0,0 +1 @@ +uid://cxm4ihth7edjf diff --git a/ChatAvatars/CatSlimes/chat_avatar.tscn b/ChatAvatars/CatSlimes/chat_avatar.tscn new file mode 100644 index 00000000..e241da43 --- /dev/null +++ b/ChatAvatars/CatSlimes/chat_avatar.tscn @@ -0,0 +1,73 @@ +[gd_scene format=3 uid="uid://cjlsmlgl6mrj6"] + +[ext_resource type="Script" uid="uid://cxm4ihth7edjf" path="res://ChatAvatars/CatSlimes/chat_avatar.gd" id="1_86yba"] +[ext_resource type="SpriteFrames" uid="uid://brmqggg5n6xv0" path="res://ChatAvatars/CatSlimes/SpriteFrames/white.tres" id="2_nuq3b"] +[ext_resource type="Script" uid="uid://c16ty2yx4qkvu" path="res://lib/state_machine/state_machine.gd" id="3_3ai6g"] +[ext_resource type="Script" uid="uid://df4p557wb3wl4" path="res://ChatAvatars/CatSlimes/avatar_states/move.gd" id="4_ujwv6"] +[ext_resource type="Script" uid="uid://ce5u2o0ht5wxi" path="res://ChatAvatars/CatSlimes/avatar_states/born.gd" id="5_788d3"] +[ext_resource type="Script" uid="uid://c356g7uw5nbg5" path="res://ChatAvatars/CatSlimes/avatar_states/idle.gd" id="6_a8imr"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_lhkd0"] +size = Vector2(32, 32) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_a8imr"] +size = Vector2(91, 32) + +[node name="ChatAvatar" type="CharacterBody2D" unique_id=2003733858] +collision_layer = 2 +script = ExtResource("1_86yba") +skin = ExtResource("2_nuq3b") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=625632016] +shape = SubResource("RectangleShape2D_lhkd0") + +[node name="Visual" type="AnimatedSprite2D" parent="." unique_id=1856553938] +unique_name_in_owner = true +sprite_frames = ExtResource("2_nuq3b") +animation = &"idle" + +[node name="DisplayName" type="Label" parent="." unique_id=672943540] +unique_name_in_owner = true +custom_minimum_size = Vector2(32, 0) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -45.5 +offset_top = -43.5 +offset_right = 45.5 +offset_bottom = -20.5 +grow_horizontal = 2 +grow_vertical = 2 +pivot_offset_ratio = Vector2(50, 50) +size_flags_horizontal = 4 +text = "Twitch User" + +[node name="StateMachine" type="Node" parent="." unique_id=69244583 node_paths=PackedStringArray("initial_state")] +unique_name_in_owner = true +script = ExtResource("3_3ai6g") +initial_state = NodePath("Born") +metadata/_custom_type_script = "uid://c16ty2yx4qkvu" + +[node name="Born" type="Node" parent="StateMachine" unique_id=1158159655 node_paths=PackedStringArray("chatter")] +script = ExtResource("5_788d3") +chatter = NodePath("../..") +metadata/_custom_type_script = "uid://ce5u2o0ht5wxi" + +[node name="Move" type="Node" parent="StateMachine" unique_id=1366338787 node_paths=PackedStringArray("chatter")] +script = ExtResource("4_ujwv6") +chatter = NodePath("../..") +metadata/_custom_type_script = "uid://df4p557wb3wl4" + +[node name="Idle" type="Node" parent="StateMachine" unique_id=1924231498 node_paths=PackedStringArray("chatter")] +script = ExtResource("6_a8imr") +chatter = NodePath("../..") +metadata/_custom_type_script = "uid://c356g7uw5nbg5" + +[node name="Overlap" type="Area2D" parent="." unique_id=1254905420] +unique_name_in_owner = true + +[node name="OverlapCollision" type="CollisionShape2D" parent="Overlap" unique_id=111157697] +unique_name_in_owner = true +shape = SubResource("RectangleShape2D_a8imr") diff --git a/ChatAvatars/CatSlimes/chat_avatar_commands.gd b/ChatAvatars/CatSlimes/chat_avatar_commands.gd new file mode 100644 index 00000000..c5f8c29c --- /dev/null +++ b/ChatAvatars/CatSlimes/chat_avatar_commands.gd @@ -0,0 +1,14 @@ +extends Node + +@export var avatars: Node2D + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + var nodes := get_children() + for node in nodes: + if node is TwitchCommand: + var cmd: TwitchCommand = node + if self.has_method("_handle_%s" % cmd.name.to_camel_case()): + cmd.command_received.connect(Callable(self, "_handle_%s" % cmd.name.to_camel_case())) + if self.has_method("_cooldown_%s" % cmd.name.to_camel_case()): + cmd.command_received.connect(Callable(self, "_cooldown_%s" % cmd.name.to_camel_case())) diff --git a/ChatAvatars/CatSlimes/chat_avatar_commands.gd.uid b/ChatAvatars/CatSlimes/chat_avatar_commands.gd.uid new file mode 100644 index 00000000..5ca51654 --- /dev/null +++ b/ChatAvatars/CatSlimes/chat_avatar_commands.gd.uid @@ -0,0 +1 @@ +uid://dlnv6wo7uxlks diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e9a1047e --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © 2024 iseesharp83 + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..31d964ef --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Kenney Spritesheet Importer + +Supports Godot 4.1/4.2 + +## Usage + +Click on a png, and choose the 'Kenney Spritesheet' option under Import. Verify the options and (re)import. + +## Files + +This will take the spritesheet and use the provided xml from Kenney (with and nodes) and create AtlasTexture resource files into the destination folder. You can then use these textures in your project. They'll be named according to the names supplied in the xml file. \ No newline at end of file diff --git a/UI/assets/FlatUI4/UIpack_vector.svg b/UI/assets/FlatUI4/UIpack_vector.svg new file mode 100644 index 00000000..6a89dc14 --- /dev/null +++ b/UI/assets/FlatUI4/UIpack_vector.svg @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UI/assets/FlatUI4/UIpack_vector.svg.import b/UI/assets/FlatUI4/UIpack_vector.svg.import new file mode 100644 index 00000000..1a8c2f3a --- /dev/null +++ b/UI/assets/FlatUI4/UIpack_vector.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d00ws7wljnit4" +path="res://.godot/imported/UIpack_vector.svg-991872993ab4122d63f28d84dc47dae8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://UI/assets/FlatUI4/UIpack_vector.svg" +dest_files=["res://.godot/imported/UIpack_vector.svg-991872993ab4122d63f28d84dc47dae8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/UI/assets/FlatUI4/kenneyUI-blue.tres b/UI/assets/FlatUI4/kenneyUI-blue.tres new file mode 100644 index 00000000..c10ef121 --- /dev/null +++ b/UI/assets/FlatUI4/kenneyUI-blue.tres @@ -0,0 +1,326 @@ +[gd_resource type="Theme" load_steps=34 format=3 uid="uid://bb6vwl0h1flws"] + +[ext_resource type="Texture2D" uid="uid://d00ws7wljnit4" path="UIpack_vector.svg" id="1"] +[ext_resource type="FontFile" uid="uid://gnbkgaf17h02" path="kenvector_future.ttf" id="2"] + +[sub_resource type="FontFile" id="31"] +fallbacks = Array[Font]([ExtResource("2")]) +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) + +[sub_resource type="StyleBoxTexture" id="1"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 240, 190, 49) + +[sub_resource type="StyleBoxEmpty" id="2"] + +[sub_resource type="StyleBoxTexture" id="3"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(1, 240, 190, 49) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 240, 190, 49) + +[sub_resource type="StyleBoxTexture" id="5"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(421, 244, 190, 45) + +[sub_resource type="AtlasTexture" id="6"] +atlas = ExtResource("1") +region = Rect2(200, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="7"] +atlas = ExtResource("1") +region = Rect2(201, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="8"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="9"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxEmpty" id="10"] + +[sub_resource type="StyleBoxEmpty" id="11"] + +[sub_resource type="StyleBoxEmpty" id="12"] + +[sub_resource type="StyleBoxEmpty" id="13"] + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("1") +region = Rect2(200, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="14"] +atlas = ExtResource("1") +region = Rect2(200, 644, 36, 36) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("1") +region = Rect2(872, 684, 28, 42) + +[sub_resource type="StyleBoxTexture" id="17"] +texture = ExtResource("1") +texture_margin_left = 3.0 +texture_margin_top = 2.0 +texture_margin_right = 2.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(840, 316, 192, 6) + +[sub_resource type="StyleBoxEmpty" id="18"] + +[sub_resource type="StyleBoxTexture" id="19"] +texture = ExtResource("1") +texture_margin_left = 17.1475 +texture_margin_top = 12.0032 +texture_margin_right = 13.718 +texture_margin_bottom = 9.43112 +region_rect = Rect2(629, 317, 194, 49) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("1") +region = Rect2(812, 458, 28, 10) + +[sub_resource type="StyleBoxTexture" id="21"] +texture = ExtResource("1") +texture_margin_left = 12.3202 +texture_margin_top = 13.3469 +texture_margin_right = 13.3469 +texture_margin_bottom = 11.2935 +region_rect = Rect2(485, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="22"] +atlas = ExtResource("1") +region = Rect2(200, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="23"] +atlas = ExtResource("1") +region = Rect2(201, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="24"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="25"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxTexture" id="26"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +region_rect = Rect2(1, 240, 190, 49) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 10.0 +content_margin_right = 10.0 +content_margin_bottom = 10.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +expand_margin_top = 10.0 +expand_margin_bottom = 10.0 +region_rect = Rect2(485, 310, 100, 100) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 12.0 +content_margin_top = 10.0 +texture = ExtResource("1") +texture_margin_left = 10.8072 +texture_margin_top = 28.4299 +texture_margin_right = 9.71325 +texture_margin_bottom = 14.7561 +axis_stretch_vertical = 1 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("1") +region = Rect2(1007, 690, 39, 31) + +[sub_resource type="StyleBoxTexture" id="30"] +texture = ExtResource("1") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(1051, 317, 4, 100) + +[resource] +default_font = SubResource("31") +Button/colors/font_color = Color(0, 0, 0, 1) +Button/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +Button/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +Button/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +Button/constants/h_separation = 2 +Button/fonts/font = SubResource("31") +Button/styles/disabled = SubResource("1") +Button/styles/focus = SubResource("2") +Button/styles/hover = SubResource("3") +Button/styles/normal = SubResource("4") +Button/styles/pressed = SubResource("5") +CheckBox/colors/font_color = Color(0, 0, 0, 1) +CheckBox/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckBox/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_hover_color_pressed = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckBox/constants/check_v_offset = 0 +CheckBox/constants/h_separation = 4 +CheckBox/fonts/font = SubResource("31") +CheckBox/icons/checked = SubResource("6") +CheckBox/icons/radio_checked = SubResource("7") +CheckBox/icons/radio_unchecked = SubResource("8") +CheckBox/icons/unchecked = SubResource("9") +CheckBox/styles/disabled = null +CheckBox/styles/focus = null +CheckBox/styles/hover = SubResource("10") +CheckBox/styles/hover_pressed = SubResource("11") +CheckBox/styles/normal = SubResource("12") +CheckBox/styles/pressed = SubResource("13") +CheckButton/colors/font_color = Color(0, 0, 0, 1) +CheckButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckButton/colors/font_hover_color_pressed = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/constants/check_v_offset = 0 +CheckButton/constants/h_separation = 4 +CheckButton/fonts/font = SubResource("31") +CheckButton/icons/checked = SubResource("15") +CheckButton/icons/checked_disabled = SubResource("15") +CheckButton/icons/unchecked = SubResource("14") +CheckButton/icons/unchecked_disabled = SubResource("14") +CheckButton/styles/disabled = null +CheckButton/styles/focus = null +CheckButton/styles/hover = null +CheckButton/styles/hover_pressed = null +CheckButton/styles/normal = null +CheckButton/styles/pressed = null +HSlider/icons/grabber = SubResource("16") +HSlider/icons/grabber_disabled = null +HSlider/icons/grabber_highlight = SubResource("16") +HSlider/icons/tick = null +HSlider/styles/grabber_area = SubResource("17") +HSlider/styles/grabber_area_highlight = SubResource("17") +HSlider/styles/slider = SubResource("17") +LineEdit/colors/clear_button_color = Color(0.88, 0.88, 0.88, 1) +LineEdit/colors/clear_button_color_pressed = Color(1, 1, 1, 1) +LineEdit/colors/cursor_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/colors/font_color = Color(0, 0, 0, 1) +LineEdit/colors/font_color_uneditable = Color(0.88, 0.88, 0.88, 0.5) +LineEdit/colors/font_selected_color = Color(0.894118, 0.894118, 0.894118, 1) +LineEdit/colors/selection_color = Color(0.117647, 0.654902, 0.882353, 1) +LineEdit/constants/minimum_spaces = 12 +LineEdit/fonts/font = SubResource("31") +LineEdit/icons/clear = null +LineEdit/styles/focus = SubResource("18") +LineEdit/styles/normal = SubResource("19") +LineEdit/styles/read_only = SubResource("19") +OptionButton/colors/font_color = Color(0, 0, 0, 1) +OptionButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +OptionButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +OptionButton/colors/font_pressed_color = Color(0, 0, 0, 1) +OptionButton/constants/arrow_margin = 2 +OptionButton/constants/h_separation = 2 +OptionButton/fonts/font = SubResource("31") +OptionButton/icons/arrow = SubResource("20") +OptionButton/styles/disabled = null +OptionButton/styles/focus = null +OptionButton/styles/hover = null +OptionButton/styles/normal = null +OptionButton/styles/pressed = null +Panel/styles/panel = SubResource("21") +PopupMenu/colors/font_color = Color(0, 0, 0, 1) +PopupMenu/colors/font_color_accel = Color(0.701961, 0.701961, 0.701961, 0.8) +PopupMenu/colors/font_disabled_color = Color(0.4, 0.4, 0.4, 0.8) +PopupMenu/colors/font_hover_color = Color(0.388235, 0.388235, 0.388235, 1) +PopupMenu/constants/h_separation = 4 +PopupMenu/constants/vseparation = 32 +PopupMenu/fonts/font = SubResource("31") +PopupMenu/icons/checked = SubResource("22") +PopupMenu/icons/radio_checked = SubResource("23") +PopupMenu/icons/radio_unchecked = SubResource("24") +PopupMenu/icons/submenu = null +PopupMenu/icons/unchecked = SubResource("25") +PopupMenu/styles/hover = SubResource("26") +PopupMenu/styles/labeled_separator_left = null +PopupMenu/styles/labeled_separator_right = null +PopupMenu/styles/panel = SubResource("27") +PopupMenu/styles/panel_disabled = null +PopupMenu/styles/separator = null +TextEdit/colors/background_color = Color(0, 0, 0, 0) +TextEdit/colors/bookmark_color = Color(0.08, 0.49, 0.98, 1) +TextEdit/colors/brace_mismatch_color = Color(1, 0.2, 0.2, 1) +TextEdit/colors/breakpoint_color = Color(0.8, 0.8, 0.4, 0.2) +TextEdit/colors/caret_background_color = Color(0, 0, 0, 1) +TextEdit/colors/caret_color = Color(0.88, 0.88, 0.88, 1) +TextEdit/colors/code_folding_color = Color(0.8, 0.8, 0.8, 0.8) +TextEdit/colors/completion_background_color = Color(0.17, 0.16, 0.2, 1) +TextEdit/colors/completion_existing_color = Color(0.87, 0.87, 0.87, 0.13) +TextEdit/colors/completion_font_color = Color(0.67, 0.67, 0.67, 1) +TextEdit/colors/completion_scroll_color = Color(1, 1, 1, 1) +TextEdit/colors/completion_selected_color = Color(0.26, 0.26, 0.27, 1) +TextEdit/colors/current_line_color = Color(0.25, 0.25, 0.26, 0.8) +TextEdit/colors/executing_line_color = Color(0.2, 0.8, 0.2, 0.4) +TextEdit/colors/font_color = Color(0, 0, 0, 1) +TextEdit/colors/font_color_readonly = Color(0.88, 0.88, 0.88, 0.5) +TextEdit/colors/font_selected_color = Color(0.34902, 0.34902, 0.34902, 1) +TextEdit/colors/function_color = Color(0.4, 0.64, 0.81, 1) +TextEdit/colors/line_number_color = Color(0.67, 0.67, 0.67, 0.4) +TextEdit/colors/mark_color = Color(1, 0.4, 0.4, 0.4) +TextEdit/colors/member_variable_color = Color(0.9, 0.31, 0.35, 1) +TextEdit/colors/number_color = Color(0.92, 0.58, 0.2, 1) +TextEdit/colors/safe_line_number_color = Color(0.67, 0.78, 0.67, 0.6) +TextEdit/colors/selection_color = Color(0.49, 0.49, 0.49, 1) +TextEdit/colors/symbol_color = Color(0.94, 0.94, 0.94, 1) +TextEdit/colors/word_highlighted_color = Color(0.8, 0.9, 0.9, 0.15) +TextEdit/constants/completion_lines = 7 +TextEdit/constants/completion_max_width = 50 +TextEdit/constants/completion_scroll_width = 3 +TextEdit/constants/line_spacing = 4 +TextEdit/fonts/font = SubResource("31") +TextEdit/icons/fold = null +TextEdit/icons/folded = null +TextEdit/icons/space = null +TextEdit/icons/tab = null +TextEdit/styles/completion = null +TextEdit/styles/focus = null +TextEdit/styles/normal = SubResource("28") +TextEdit/styles/read_only = null +VSlider/icons/grabber = SubResource("29") +VSlider/icons/grabber_disabled = SubResource("29") +VSlider/icons/grabber_highlight = SubResource("29") +VSlider/icons/tick = null +VSlider/styles/grabber_area = SubResource("30") +VSlider/styles/grabber_area_highlight = SubResource("30") +VSlider/styles/slider = SubResource("30") diff --git a/UI/assets/FlatUI4/kenneyUI-green.tres b/UI/assets/FlatUI4/kenneyUI-green.tres new file mode 100644 index 00000000..6dc9022e --- /dev/null +++ b/UI/assets/FlatUI4/kenneyUI-green.tres @@ -0,0 +1,326 @@ +[gd_resource type="Theme" load_steps=34 format=3 uid="uid://bnhngmri4tfgr"] + +[ext_resource type="Texture2D" uid="uid://d00ws7wljnit4" path="UIpack_vector.svg" id="1"] +[ext_resource type="FontFile" uid="uid://gnbkgaf17h02" path="kenvector_future.ttf" id="2"] + +[sub_resource type="FontFile" id="31"] +fallbacks = Array[Font]([ExtResource("2")]) +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) + +[sub_resource type="StyleBoxTexture" id="1"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 120, 190, 49) + +[sub_resource type="StyleBoxEmpty" id="2"] + +[sub_resource type="StyleBoxTexture" id="3"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(1, 120, 190, 49) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 120, 190, 49) + +[sub_resource type="StyleBoxTexture" id="5"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(421, 124, 190, 45) + +[sub_resource type="AtlasTexture" id="6"] +atlas = ExtResource("1") +region = Rect2(100, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="7"] +atlas = ExtResource("1") +region = Rect2(101, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="8"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="9"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxEmpty" id="10"] + +[sub_resource type="StyleBoxEmpty" id="11"] + +[sub_resource type="StyleBoxEmpty" id="12"] + +[sub_resource type="StyleBoxEmpty" id="13"] + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("1") +region = Rect2(100, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="14"] +atlas = ExtResource("1") +region = Rect2(100, 644, 36, 36) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("1") +region = Rect2(872, 564, 28, 42) + +[sub_resource type="StyleBoxTexture" id="17"] +texture = ExtResource("1") +texture_margin_left = 2.0 +texture_margin_top = 2.0 +texture_margin_right = 2.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(840, 316, 192, 6) + +[sub_resource type="StyleBoxEmpty" id="18"] + +[sub_resource type="StyleBoxTexture" id="19"] +texture = ExtResource("1") +texture_margin_left = 17.1475 +texture_margin_top = 12.0032 +texture_margin_right = 13.718 +texture_margin_bottom = 9.43112 +region_rect = Rect2(629, 317, 194, 49) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("1") +region = Rect2(816, 578, 20, 10) + +[sub_resource type="StyleBoxTexture" id="21"] +texture = ExtResource("1") +texture_margin_left = 12.3202 +texture_margin_top = 13.3469 +texture_margin_right = 13.3469 +texture_margin_bottom = 11.2935 +region_rect = Rect2(243, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="22"] +atlas = ExtResource("1") +region = Rect2(100, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="23"] +atlas = ExtResource("1") +region = Rect2(101, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="24"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="25"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxTexture" id="26"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +region_rect = Rect2(1, 120, 190, 49) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 10.0 +content_margin_right = 10.0 +content_margin_bottom = 10.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +expand_margin_top = 10.0 +expand_margin_bottom = 10.0 +region_rect = Rect2(243, 310, 100, 100) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 12.0 +content_margin_top = 10.0 +texture = ExtResource("1") +texture_margin_left = 10.8072 +texture_margin_top = 28.4299 +texture_margin_right = 9.71325 +texture_margin_bottom = 14.7561 +axis_stretch_vertical = 1 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("1") +region = Rect2(1007, 570, 39, 31) + +[sub_resource type="StyleBoxTexture" id="30"] +texture = ExtResource("1") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(1051, 318, 4, 99) + +[resource] +default_font = SubResource("31") +Button/colors/font_color = Color(0, 0, 0, 1) +Button/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +Button/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +Button/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +Button/constants/h_separation = 2 +Button/fonts/font = SubResource("31") +Button/styles/disabled = SubResource("1") +Button/styles/focus = SubResource("2") +Button/styles/hover = SubResource("3") +Button/styles/normal = SubResource("4") +Button/styles/pressed = SubResource("5") +CheckBox/colors/font_color = Color(0, 0, 0, 1) +CheckBox/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckBox/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_hover_color_pressed = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckBox/constants/check_v_offset = 0 +CheckBox/constants/h_separation = 4 +CheckBox/fonts/font = SubResource("31") +CheckBox/icons/checked = SubResource("6") +CheckBox/icons/radio_checked = SubResource("7") +CheckBox/icons/radio_unchecked = SubResource("8") +CheckBox/icons/unchecked = SubResource("9") +CheckBox/styles/disabled = null +CheckBox/styles/focus = null +CheckBox/styles/hover = SubResource("10") +CheckBox/styles/hover_pressed = SubResource("11") +CheckBox/styles/normal = SubResource("12") +CheckBox/styles/pressed = SubResource("13") +CheckButton/colors/font_color = Color(0, 0, 0, 1) +CheckButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckButton/colors/font_hover_color_pressed = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/constants/check_v_offset = 0 +CheckButton/constants/h_separation = 4 +CheckButton/fonts/font = SubResource("31") +CheckButton/icons/checked = SubResource("15") +CheckButton/icons/checked_disabled = SubResource("15") +CheckButton/icons/unchecked = SubResource("14") +CheckButton/icons/unchecked_disabled = SubResource("14") +CheckButton/styles/disabled = null +CheckButton/styles/focus = null +CheckButton/styles/hover = null +CheckButton/styles/hover_pressed = null +CheckButton/styles/normal = null +CheckButton/styles/pressed = null +HSlider/icons/grabber = SubResource("16") +HSlider/icons/grabber_disabled = null +HSlider/icons/grabber_highlight = SubResource("16") +HSlider/icons/tick = null +HSlider/styles/grabber_area = SubResource("17") +HSlider/styles/grabber_area_highlight = SubResource("17") +HSlider/styles/slider = SubResource("17") +LineEdit/colors/clear_button_color = Color(0.88, 0.88, 0.88, 1) +LineEdit/colors/clear_button_color_pressed = Color(1, 1, 1, 1) +LineEdit/colors/cursor_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/colors/font_color = Color(0, 0, 0, 1) +LineEdit/colors/font_color_uneditable = Color(0.88, 0.88, 0.88, 0.5) +LineEdit/colors/font_selected_color = Color(0.894118, 0.894118, 0.894118, 1) +LineEdit/colors/selection_color = Color(0.45098, 0.803922, 0.294118, 1) +LineEdit/constants/minimum_spaces = 12 +LineEdit/fonts/font = SubResource("31") +LineEdit/icons/clear = null +LineEdit/styles/focus = SubResource("18") +LineEdit/styles/normal = SubResource("19") +LineEdit/styles/read_only = SubResource("19") +OptionButton/colors/font_color = Color(0, 0, 0, 1) +OptionButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +OptionButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +OptionButton/colors/font_pressed_color = Color(0, 0, 0, 1) +OptionButton/constants/arrow_margin = 2 +OptionButton/constants/h_separation = 2 +OptionButton/fonts/font = SubResource("31") +OptionButton/icons/arrow = SubResource("20") +OptionButton/styles/disabled = null +OptionButton/styles/focus = null +OptionButton/styles/hover = null +OptionButton/styles/normal = null +OptionButton/styles/pressed = null +Panel/styles/panel = SubResource("21") +PopupMenu/colors/font_color = Color(0, 0, 0, 1) +PopupMenu/colors/font_color_accel = Color(0.701961, 0.701961, 0.701961, 0.8) +PopupMenu/colors/font_disabled_color = Color(0.4, 0.4, 0.4, 0.8) +PopupMenu/colors/font_hover_color = Color(0.388235, 0.388235, 0.388235, 1) +PopupMenu/constants/h_separation = 4 +PopupMenu/constants/vseparation = 32 +PopupMenu/fonts/font = SubResource("31") +PopupMenu/icons/checked = SubResource("22") +PopupMenu/icons/radio_checked = SubResource("23") +PopupMenu/icons/radio_unchecked = SubResource("24") +PopupMenu/icons/submenu = null +PopupMenu/icons/unchecked = SubResource("25") +PopupMenu/styles/hover = SubResource("26") +PopupMenu/styles/labeled_separator_left = null +PopupMenu/styles/labeled_separator_right = null +PopupMenu/styles/panel = SubResource("27") +PopupMenu/styles/panel_disabled = null +PopupMenu/styles/separator = null +TextEdit/colors/background_color = Color(0, 0, 0, 0) +TextEdit/colors/bookmark_color = Color(0.08, 0.49, 0.98, 1) +TextEdit/colors/brace_mismatch_color = Color(1, 0.2, 0.2, 1) +TextEdit/colors/breakpoint_color = Color(0.8, 0.8, 0.4, 0.2) +TextEdit/colors/caret_background_color = Color(0, 0, 0, 1) +TextEdit/colors/caret_color = Color(0.88, 0.88, 0.88, 1) +TextEdit/colors/code_folding_color = Color(0.8, 0.8, 0.8, 0.8) +TextEdit/colors/completion_background_color = Color(0.17, 0.16, 0.2, 1) +TextEdit/colors/completion_existing_color = Color(0.87, 0.87, 0.87, 0.13) +TextEdit/colors/completion_font_color = Color(0.67, 0.67, 0.67, 1) +TextEdit/colors/completion_scroll_color = Color(1, 1, 1, 1) +TextEdit/colors/completion_selected_color = Color(0.26, 0.26, 0.27, 1) +TextEdit/colors/current_line_color = Color(0.25, 0.25, 0.26, 0.8) +TextEdit/colors/executing_line_color = Color(0.2, 0.8, 0.2, 0.4) +TextEdit/colors/font_color = Color(0, 0, 0, 1) +TextEdit/colors/font_color_readonly = Color(0.88, 0.88, 0.88, 0.5) +TextEdit/colors/font_selected_color = Color(0.34902, 0.34902, 0.34902, 1) +TextEdit/colors/function_color = Color(0.4, 0.64, 0.81, 1) +TextEdit/colors/line_number_color = Color(0.67, 0.67, 0.67, 0.4) +TextEdit/colors/mark_color = Color(1, 0.4, 0.4, 0.4) +TextEdit/colors/member_variable_color = Color(0.9, 0.31, 0.35, 1) +TextEdit/colors/number_color = Color(0.92, 0.58, 0.2, 1) +TextEdit/colors/safe_line_number_color = Color(0.67, 0.78, 0.67, 0.6) +TextEdit/colors/selection_color = Color(0.49, 0.49, 0.49, 1) +TextEdit/colors/symbol_color = Color(0.94, 0.94, 0.94, 1) +TextEdit/colors/word_highlighted_color = Color(0.8, 0.9, 0.9, 0.15) +TextEdit/constants/completion_lines = 7 +TextEdit/constants/completion_max_width = 50 +TextEdit/constants/completion_scroll_width = 3 +TextEdit/constants/line_spacing = 4 +TextEdit/fonts/font = SubResource("31") +TextEdit/icons/fold = null +TextEdit/icons/folded = null +TextEdit/icons/space = null +TextEdit/icons/tab = null +TextEdit/styles/completion = null +TextEdit/styles/focus = null +TextEdit/styles/normal = SubResource("28") +TextEdit/styles/read_only = null +VSlider/icons/grabber = SubResource("29") +VSlider/icons/grabber_disabled = SubResource("29") +VSlider/icons/grabber_highlight = SubResource("29") +VSlider/icons/tick = null +VSlider/styles/grabber_area = SubResource("30") +VSlider/styles/grabber_area_highlight = SubResource("30") +VSlider/styles/slider = SubResource("30") diff --git a/UI/assets/FlatUI4/kenneyUI-red.tres b/UI/assets/FlatUI4/kenneyUI-red.tres new file mode 100644 index 00000000..77a38d65 --- /dev/null +++ b/UI/assets/FlatUI4/kenneyUI-red.tres @@ -0,0 +1,326 @@ +[gd_resource type="Theme" load_steps=34 format=3 uid="uid://mbsg4xqnedxi"] + +[ext_resource type="Texture2D" uid="uid://d00ws7wljnit4" path="UIpack_vector.svg" id="1"] +[ext_resource type="FontFile" uid="uid://gnbkgaf17h02" path="kenvector_future.ttf" id="2"] + +[sub_resource type="FontFile" id="31"] +fallbacks = Array[Font]([ExtResource("2")]) +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) + +[sub_resource type="StyleBoxTexture" id="1"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 180, 190, 49) + +[sub_resource type="StyleBoxEmpty" id="2"] + +[sub_resource type="StyleBoxTexture" id="3"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(1, 180, 190, 49) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 180, 190, 49) + +[sub_resource type="StyleBoxTexture" id="5"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(421, 184, 190, 45) + +[sub_resource type="AtlasTexture" id="6"] +atlas = ExtResource("1") +region = Rect2(150, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="7"] +atlas = ExtResource("1") +region = Rect2(151, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="8"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="9"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxEmpty" id="10"] + +[sub_resource type="StyleBoxEmpty" id="11"] + +[sub_resource type="StyleBoxEmpty" id="12"] + +[sub_resource type="StyleBoxEmpty" id="13"] + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("1") +region = Rect2(150, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="14"] +atlas = ExtResource("1") +region = Rect2(150, 644, 36, 36) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("1") +region = Rect2(872, 624, 28, 42) + +[sub_resource type="StyleBoxTexture" id="17"] +texture = ExtResource("1") +texture_margin_left = 2.0 +texture_margin_top = 2.0 +texture_margin_right = 2.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(840, 316, 192, 6) + +[sub_resource type="StyleBoxEmpty" id="18"] + +[sub_resource type="StyleBoxTexture" id="19"] +texture = ExtResource("1") +texture_margin_left = 17.1475 +texture_margin_top = 12.0032 +texture_margin_right = 13.718 +texture_margin_bottom = 9.43112 +region_rect = Rect2(629, 317, 194, 49) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("1") +region = Rect2(814, 578, 24, 10) + +[sub_resource type="StyleBoxTexture" id="21"] +texture = ExtResource("1") +texture_margin_left = 12.3202 +texture_margin_top = 13.3469 +texture_margin_right = 13.3469 +texture_margin_bottom = 11.2935 +region_rect = Rect2(364, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="22"] +atlas = ExtResource("1") +region = Rect2(150, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="23"] +atlas = ExtResource("1") +region = Rect2(151, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="24"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="25"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxTexture" id="26"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +region_rect = Rect2(1, 180, 190, 49) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 10.0 +content_margin_right = 10.0 +content_margin_bottom = 10.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +expand_margin_top = 10.0 +expand_margin_bottom = 10.0 +region_rect = Rect2(364, 310, 100, 100) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 12.0 +content_margin_top = 10.0 +texture = ExtResource("1") +texture_margin_left = 10.8072 +texture_margin_top = 28.4299 +texture_margin_right = 9.71325 +texture_margin_bottom = 14.7561 +axis_stretch_vertical = 1 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("1") +region = Rect2(1007, 630, 39, 31) + +[sub_resource type="StyleBoxTexture" id="30"] +texture = ExtResource("1") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(1051, 317, 4, 100) + +[resource] +default_font = SubResource("31") +Button/colors/font_color = Color(0, 0, 0, 1) +Button/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +Button/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +Button/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +Button/constants/h_separation = 2 +Button/fonts/font = SubResource("31") +Button/styles/disabled = SubResource("1") +Button/styles/focus = SubResource("2") +Button/styles/hover = SubResource("3") +Button/styles/normal = SubResource("4") +Button/styles/pressed = SubResource("5") +CheckBox/colors/font_color = Color(0, 0, 0, 1) +CheckBox/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckBox/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_hover_color_pressed = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckBox/constants/check_v_offset = 0 +CheckBox/constants/h_separation = 4 +CheckBox/fonts/font = SubResource("31") +CheckBox/icons/checked = SubResource("6") +CheckBox/icons/radio_checked = SubResource("7") +CheckBox/icons/radio_unchecked = SubResource("8") +CheckBox/icons/unchecked = SubResource("9") +CheckBox/styles/disabled = null +CheckBox/styles/focus = null +CheckBox/styles/hover = SubResource("10") +CheckBox/styles/hover_pressed = SubResource("11") +CheckBox/styles/normal = SubResource("12") +CheckBox/styles/pressed = SubResource("13") +CheckButton/colors/font_color = Color(0, 0, 0, 1) +CheckButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckButton/colors/font_hover_color_pressed = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/constants/check_v_offset = 0 +CheckButton/constants/h_separation = 4 +CheckButton/fonts/font = SubResource("31") +CheckButton/icons/checked = SubResource("15") +CheckButton/icons/checked_disabled = SubResource("15") +CheckButton/icons/unchecked = SubResource("14") +CheckButton/icons/unchecked_disabled = SubResource("14") +CheckButton/styles/disabled = null +CheckButton/styles/focus = null +CheckButton/styles/hover = null +CheckButton/styles/hover_pressed = null +CheckButton/styles/normal = null +CheckButton/styles/pressed = null +HSlider/icons/grabber = SubResource("16") +HSlider/icons/grabber_disabled = null +HSlider/icons/grabber_highlight = SubResource("16") +HSlider/icons/tick = null +HSlider/styles/grabber_area = SubResource("17") +HSlider/styles/grabber_area_highlight = SubResource("17") +HSlider/styles/slider = SubResource("17") +LineEdit/colors/clear_button_color = Color(0.88, 0.88, 0.88, 1) +LineEdit/colors/clear_button_color_pressed = Color(1, 1, 1, 1) +LineEdit/colors/cursor_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/colors/font_color = Color(0, 0, 0, 1) +LineEdit/colors/font_color_uneditable = Color(0.88, 0.88, 0.88, 0.5) +LineEdit/colors/font_selected_color = Color(0.894118, 0.894118, 0.894118, 1) +LineEdit/colors/selection_color = Color(0.909804, 0.415686, 0.0901961, 1) +LineEdit/constants/minimum_spaces = 12 +LineEdit/fonts/font = SubResource("31") +LineEdit/icons/clear = null +LineEdit/styles/focus = SubResource("18") +LineEdit/styles/normal = SubResource("19") +LineEdit/styles/read_only = SubResource("19") +OptionButton/colors/font_color = Color(0, 0, 0, 1) +OptionButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +OptionButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +OptionButton/colors/font_pressed_color = Color(0, 0, 0, 1) +OptionButton/constants/arrow_margin = 2 +OptionButton/constants/h_separation = 2 +OptionButton/fonts/font = SubResource("31") +OptionButton/icons/arrow = SubResource("20") +OptionButton/styles/disabled = null +OptionButton/styles/focus = null +OptionButton/styles/hover = null +OptionButton/styles/normal = null +OptionButton/styles/pressed = null +Panel/styles/panel = SubResource("21") +PopupMenu/colors/font_color = Color(0, 0, 0, 1) +PopupMenu/colors/font_color_accel = Color(0.701961, 0.701961, 0.701961, 0.8) +PopupMenu/colors/font_disabled_color = Color(0.4, 0.4, 0.4, 0.8) +PopupMenu/colors/font_hover_color = Color(0.388235, 0.388235, 0.388235, 1) +PopupMenu/constants/h_separation = 4 +PopupMenu/constants/vseparation = 32 +PopupMenu/fonts/font = SubResource("31") +PopupMenu/icons/checked = SubResource("22") +PopupMenu/icons/radio_checked = SubResource("23") +PopupMenu/icons/radio_unchecked = SubResource("24") +PopupMenu/icons/submenu = null +PopupMenu/icons/unchecked = SubResource("25") +PopupMenu/styles/hover = SubResource("26") +PopupMenu/styles/labeled_separator_left = null +PopupMenu/styles/labeled_separator_right = null +PopupMenu/styles/panel = SubResource("27") +PopupMenu/styles/panel_disabled = null +PopupMenu/styles/separator = null +TextEdit/colors/background_color = Color(0, 0, 0, 0) +TextEdit/colors/bookmark_color = Color(0.08, 0.49, 0.98, 1) +TextEdit/colors/brace_mismatch_color = Color(1, 0.2, 0.2, 1) +TextEdit/colors/breakpoint_color = Color(0.8, 0.8, 0.4, 0.2) +TextEdit/colors/caret_background_color = Color(0, 0, 0, 1) +TextEdit/colors/caret_color = Color(0.88, 0.88, 0.88, 1) +TextEdit/colors/code_folding_color = Color(0.8, 0.8, 0.8, 0.8) +TextEdit/colors/completion_background_color = Color(0.17, 0.16, 0.2, 1) +TextEdit/colors/completion_existing_color = Color(0.87, 0.87, 0.87, 0.13) +TextEdit/colors/completion_font_color = Color(0.67, 0.67, 0.67, 1) +TextEdit/colors/completion_scroll_color = Color(1, 1, 1, 1) +TextEdit/colors/completion_selected_color = Color(0.26, 0.26, 0.27, 1) +TextEdit/colors/current_line_color = Color(0.25, 0.25, 0.26, 0.8) +TextEdit/colors/executing_line_color = Color(0.2, 0.8, 0.2, 0.4) +TextEdit/colors/font_color = Color(0, 0, 0, 1) +TextEdit/colors/font_color_readonly = Color(0.88, 0.88, 0.88, 0.5) +TextEdit/colors/font_selected_color = Color(0.34902, 0.34902, 0.34902, 1) +TextEdit/colors/function_color = Color(0.4, 0.64, 0.81, 1) +TextEdit/colors/line_number_color = Color(0.67, 0.67, 0.67, 0.4) +TextEdit/colors/mark_color = Color(1, 0.4, 0.4, 0.4) +TextEdit/colors/member_variable_color = Color(0.9, 0.31, 0.35, 1) +TextEdit/colors/number_color = Color(0.92, 0.58, 0.2, 1) +TextEdit/colors/safe_line_number_color = Color(0.67, 0.78, 0.67, 0.6) +TextEdit/colors/selection_color = Color(0.49, 0.49, 0.49, 1) +TextEdit/colors/symbol_color = Color(0.94, 0.94, 0.94, 1) +TextEdit/colors/word_highlighted_color = Color(0.8, 0.9, 0.9, 0.15) +TextEdit/constants/completion_lines = 7 +TextEdit/constants/completion_max_width = 50 +TextEdit/constants/completion_scroll_width = 3 +TextEdit/constants/line_spacing = 4 +TextEdit/fonts/font = SubResource("31") +TextEdit/icons/fold = null +TextEdit/icons/folded = null +TextEdit/icons/space = null +TextEdit/icons/tab = null +TextEdit/styles/completion = null +TextEdit/styles/focus = null +TextEdit/styles/normal = SubResource("28") +TextEdit/styles/read_only = null +VSlider/icons/grabber = SubResource("29") +VSlider/icons/grabber_disabled = SubResource("29") +VSlider/icons/grabber_highlight = SubResource("29") +VSlider/icons/tick = null +VSlider/styles/grabber_area = SubResource("30") +VSlider/styles/grabber_area_highlight = SubResource("30") +VSlider/styles/slider = SubResource("30") diff --git a/UI/assets/FlatUI4/kenneyUI-yellow.tres b/UI/assets/FlatUI4/kenneyUI-yellow.tres new file mode 100644 index 00000000..0e319a0c --- /dev/null +++ b/UI/assets/FlatUI4/kenneyUI-yellow.tres @@ -0,0 +1,326 @@ +[gd_resource type="Theme" load_steps=34 format=3 uid="uid://h1f7iqhh7ycb"] + +[ext_resource type="Texture2D" uid="uid://d00ws7wljnit4" path="UIpack_vector.svg" id="1"] +[ext_resource type="FontFile" uid="uid://gnbkgaf17h02" path="kenvector_future.ttf" id="2"] + +[sub_resource type="FontFile" id="31"] +fallbacks = Array[Font]([ExtResource("2")]) +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) + +[sub_resource type="StyleBoxTexture" id="1"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 60, 190, 49) + +[sub_resource type="StyleBoxEmpty" id="2"] + +[sub_resource type="StyleBoxTexture" id="3"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(1, 60, 190, 49) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 60, 190, 49) + +[sub_resource type="StyleBoxTexture" id="5"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(421, 64, 190, 45) + +[sub_resource type="AtlasTexture" id="6"] +atlas = ExtResource("1") +region = Rect2(50, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="7"] +atlas = ExtResource("1") +region = Rect2(51, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="8"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="9"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxEmpty" id="10"] + +[sub_resource type="StyleBoxEmpty" id="11"] + +[sub_resource type="StyleBoxEmpty" id="12"] + +[sub_resource type="StyleBoxEmpty" id="13"] + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("1") +region = Rect2(50, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="14"] +atlas = ExtResource("1") +region = Rect2(50, 644, 36, 36) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("1") +region = Rect2(872, 504, 28, 42) + +[sub_resource type="StyleBoxTexture" id="17"] +texture = ExtResource("1") +texture_margin_left = 2.0 +texture_margin_top = 2.0 +texture_margin_right = 2.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(840, 316, 192, 6) + +[sub_resource type="StyleBoxEmpty" id="18"] + +[sub_resource type="StyleBoxTexture" id="19"] +texture = ExtResource("1") +texture_margin_left = 17.1475 +texture_margin_top = 12.0032 +texture_margin_right = 13.718 +texture_margin_bottom = 9.43112 +region_rect = Rect2(629, 317, 194, 49) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("1") +region = Rect2(812, 458, 28, 10) + +[sub_resource type="StyleBoxTexture" id="21"] +texture = ExtResource("1") +texture_margin_left = 12.3202 +texture_margin_top = 13.3469 +texture_margin_right = 13.3469 +texture_margin_bottom = 11.2935 +region_rect = Rect2(122, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="22"] +atlas = ExtResource("1") +region = Rect2(50, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="23"] +atlas = ExtResource("1") +region = Rect2(51, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="24"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="25"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxTexture" id="26"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +region_rect = Rect2(1, 60, 190, 49) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 10.0 +content_margin_right = 10.0 +content_margin_bottom = 10.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +expand_margin_top = 10.0 +expand_margin_bottom = 10.0 +region_rect = Rect2(122, 310, 100, 100) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 12.0 +content_margin_top = 10.0 +texture = ExtResource("1") +texture_margin_left = 10.8072 +texture_margin_top = 28.4299 +texture_margin_right = 9.71325 +texture_margin_bottom = 14.7561 +axis_stretch_vertical = 1 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("1") +region = Rect2(1007, 510, 39, 31) + +[sub_resource type="StyleBoxTexture" id="30"] +texture = ExtResource("1") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(1051, 317, 4, 100) + +[resource] +default_font = SubResource("31") +Button/colors/font_color = Color(0, 0, 0, 1) +Button/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +Button/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +Button/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +Button/constants/h_separation = 2 +Button/fonts/font = SubResource("31") +Button/styles/disabled = SubResource("1") +Button/styles/focus = SubResource("2") +Button/styles/hover = SubResource("3") +Button/styles/normal = SubResource("4") +Button/styles/pressed = SubResource("5") +CheckBox/colors/font_color = Color(0, 0, 0, 1) +CheckBox/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckBox/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_hover_color_pressed = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckBox/constants/check_v_offset = 0 +CheckBox/constants/h_separation = 4 +CheckBox/fonts/font = SubResource("31") +CheckBox/icons/checked = SubResource("6") +CheckBox/icons/radio_checked = SubResource("7") +CheckBox/icons/radio_unchecked = SubResource("8") +CheckBox/icons/unchecked = SubResource("9") +CheckBox/styles/disabled = null +CheckBox/styles/focus = null +CheckBox/styles/hover = SubResource("10") +CheckBox/styles/hover_pressed = SubResource("11") +CheckBox/styles/normal = SubResource("12") +CheckBox/styles/pressed = SubResource("13") +CheckButton/colors/font_color = Color(0, 0, 0, 1) +CheckButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckButton/colors/font_hover_color_pressed = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/constants/check_v_offset = 0 +CheckButton/constants/h_separation = 4 +CheckButton/fonts/font = SubResource("31") +CheckButton/icons/checked = SubResource("15") +CheckButton/icons/checked_disabled = SubResource("15") +CheckButton/icons/unchecked = SubResource("14") +CheckButton/icons/unchecked_disabled = SubResource("14") +CheckButton/styles/disabled = null +CheckButton/styles/focus = null +CheckButton/styles/hover = null +CheckButton/styles/hover_pressed = null +CheckButton/styles/normal = null +CheckButton/styles/pressed = null +HSlider/icons/grabber = SubResource("16") +HSlider/icons/grabber_disabled = null +HSlider/icons/grabber_highlight = SubResource("16") +HSlider/icons/tick = null +HSlider/styles/grabber_area = SubResource("17") +HSlider/styles/grabber_area_highlight = SubResource("17") +HSlider/styles/slider = SubResource("17") +LineEdit/colors/clear_button_color = Color(0.88, 0.88, 0.88, 1) +LineEdit/colors/clear_button_color_pressed = Color(1, 1, 1, 1) +LineEdit/colors/cursor_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/colors/font_color = Color(0, 0, 0, 1) +LineEdit/colors/font_color_uneditable = Color(0.88, 0.88, 0.88, 0.5) +LineEdit/colors/font_selected_color = Color(0, 0, 0, 1) +LineEdit/colors/selection_color = Color(1, 0.8, 0, 1) +LineEdit/constants/minimum_spaces = 12 +LineEdit/fonts/font = SubResource("31") +LineEdit/icons/clear = null +LineEdit/styles/focus = SubResource("18") +LineEdit/styles/normal = SubResource("19") +LineEdit/styles/read_only = SubResource("19") +OptionButton/colors/font_color = Color(0, 0, 0, 1) +OptionButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +OptionButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +OptionButton/colors/font_pressed_color = Color(0, 0, 0, 1) +OptionButton/constants/arrow_margin = 2 +OptionButton/constants/h_separation = 2 +OptionButton/fonts/font = SubResource("31") +OptionButton/icons/arrow = SubResource("20") +OptionButton/styles/disabled = null +OptionButton/styles/focus = null +OptionButton/styles/hover = null +OptionButton/styles/normal = null +OptionButton/styles/pressed = null +Panel/styles/panel = SubResource("21") +PopupMenu/colors/font_color = Color(0, 0, 0, 1) +PopupMenu/colors/font_color_accel = Color(0.701961, 0.701961, 0.701961, 0.8) +PopupMenu/colors/font_disabled_color = Color(0.4, 0.4, 0.4, 0.8) +PopupMenu/colors/font_hover_color = Color(0.388235, 0.388235, 0.388235, 1) +PopupMenu/constants/h_separation = 4 +PopupMenu/constants/vseparation = 32 +PopupMenu/fonts/font = SubResource("31") +PopupMenu/icons/checked = SubResource("22") +PopupMenu/icons/radio_checked = SubResource("23") +PopupMenu/icons/radio_unchecked = SubResource("24") +PopupMenu/icons/submenu = null +PopupMenu/icons/unchecked = SubResource("25") +PopupMenu/styles/hover = SubResource("26") +PopupMenu/styles/labeled_separator_left = null +PopupMenu/styles/labeled_separator_right = null +PopupMenu/styles/panel = SubResource("27") +PopupMenu/styles/panel_disabled = null +PopupMenu/styles/separator = null +TextEdit/colors/background_color = Color(0, 0, 0, 0) +TextEdit/colors/bookmark_color = Color(0.08, 0.49, 0.98, 1) +TextEdit/colors/brace_mismatch_color = Color(1, 0.2, 0.2, 1) +TextEdit/colors/breakpoint_color = Color(0.8, 0.8, 0.4, 0.2) +TextEdit/colors/caret_background_color = Color(0, 0, 0, 1) +TextEdit/colors/caret_color = Color(0.88, 0.88, 0.88, 1) +TextEdit/colors/code_folding_color = Color(0.8, 0.8, 0.8, 0.8) +TextEdit/colors/completion_background_color = Color(0.17, 0.16, 0.2, 1) +TextEdit/colors/completion_existing_color = Color(0.87, 0.87, 0.87, 0.13) +TextEdit/colors/completion_font_color = Color(0.67, 0.67, 0.67, 1) +TextEdit/colors/completion_scroll_color = Color(1, 1, 1, 1) +TextEdit/colors/completion_selected_color = Color(0.26, 0.26, 0.27, 1) +TextEdit/colors/current_line_color = Color(0.25, 0.25, 0.26, 0.8) +TextEdit/colors/executing_line_color = Color(0.2, 0.8, 0.2, 0.4) +TextEdit/colors/font_color = Color(0, 0, 0, 1) +TextEdit/colors/font_color_readonly = Color(0.88, 0.88, 0.88, 0.5) +TextEdit/colors/font_selected_color = Color(0.34902, 0.34902, 0.34902, 1) +TextEdit/colors/function_color = Color(0.4, 0.64, 0.81, 1) +TextEdit/colors/line_number_color = Color(0.67, 0.67, 0.67, 0.4) +TextEdit/colors/mark_color = Color(1, 0.4, 0.4, 0.4) +TextEdit/colors/member_variable_color = Color(0.9, 0.31, 0.35, 1) +TextEdit/colors/number_color = Color(0.92, 0.58, 0.2, 1) +TextEdit/colors/safe_line_number_color = Color(0.67, 0.78, 0.67, 0.6) +TextEdit/colors/selection_color = Color(0.49, 0.49, 0.49, 1) +TextEdit/colors/symbol_color = Color(0.94, 0.94, 0.94, 1) +TextEdit/colors/word_highlighted_color = Color(0.8, 0.9, 0.9, 0.15) +TextEdit/constants/completion_lines = 7 +TextEdit/constants/completion_max_width = 50 +TextEdit/constants/completion_scroll_width = 3 +TextEdit/constants/line_spacing = 4 +TextEdit/fonts/font = SubResource("31") +TextEdit/icons/fold = null +TextEdit/icons/folded = null +TextEdit/icons/space = null +TextEdit/icons/tab = null +TextEdit/styles/completion = null +TextEdit/styles/focus = null +TextEdit/styles/normal = SubResource("28") +TextEdit/styles/read_only = null +VSlider/icons/grabber = SubResource("29") +VSlider/icons/grabber_disabled = SubResource("29") +VSlider/icons/grabber_highlight = SubResource("29") +VSlider/icons/tick = null +VSlider/styles/grabber_area = SubResource("30") +VSlider/styles/grabber_area_highlight = SubResource("30") +VSlider/styles/slider = SubResource("30") diff --git a/UI/assets/FlatUI4/kenneyUI.tres b/UI/assets/FlatUI4/kenneyUI.tres new file mode 100644 index 00000000..5f0a71c7 --- /dev/null +++ b/UI/assets/FlatUI4/kenneyUI.tres @@ -0,0 +1,326 @@ +[gd_resource type="Theme" load_steps=34 format=3 uid="uid://6shp5ck1tnja"] + +[ext_resource type="Texture2D" uid="uid://d00ws7wljnit4" path="UIpack_vector.svg" id="1"] +[ext_resource type="FontFile" uid="uid://gnbkgaf17h02" path="kenvector_future.ttf" id="2"] + +[sub_resource type="FontFile" id="31"] +fallbacks = Array[Font]([ExtResource("2")]) +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) + +[sub_resource type="StyleBoxTexture" id="1"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 0, 190, 49) + +[sub_resource type="StyleBoxEmpty" id="2"] + +[sub_resource type="StyleBoxTexture" id="3"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(1, 0, 190, 49) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(211, 0, 190, 49) + +[sub_resource type="StyleBoxTexture" id="5"] +texture = ExtResource("1") +texture_margin_left = 12.5436 +texture_margin_top = 10.0349 +texture_margin_right = 8.78053 +texture_margin_bottom = 14.2161 +region_rect = Rect2(421, 4, 190, 45) + +[sub_resource type="AtlasTexture" id="6"] +atlas = ExtResource("1") +region = Rect2(250, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="7"] +atlas = ExtResource("1") +region = Rect2(251, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="8"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="9"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxEmpty" id="10"] + +[sub_resource type="StyleBoxEmpty" id="11"] + +[sub_resource type="StyleBoxEmpty" id="12"] + +[sub_resource type="StyleBoxEmpty" id="13"] + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("1") +region = Rect2(250, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="14"] +atlas = ExtResource("1") +region = Rect2(250, 644, 36, 36) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("1") +region = Rect2(872, 444, 28, 42) + +[sub_resource type="StyleBoxTexture" id="17"] +texture = ExtResource("1") +texture_margin_left = 2.0 +texture_margin_top = 2.0 +texture_margin_right = 2.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(840, 316, 192, 6) + +[sub_resource type="StyleBoxEmpty" id="18"] + +[sub_resource type="StyleBoxTexture" id="19"] +texture = ExtResource("1") +texture_margin_left = 17.1475 +texture_margin_top = 12.0032 +texture_margin_right = 13.718 +texture_margin_bottom = 9.43112 +region_rect = Rect2(629, 317, 194, 49) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("1") +region = Rect2(812, 458, 28, 10) + +[sub_resource type="StyleBoxTexture" id="21"] +texture = ExtResource("1") +texture_margin_left = 12.3202 +texture_margin_top = 13.3469 +texture_margin_right = 13.3469 +texture_margin_bottom = 11.2935 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="22"] +atlas = ExtResource("1") +region = Rect2(250, 544, 36, 36) + +[sub_resource type="AtlasTexture" id="23"] +atlas = ExtResource("1") +region = Rect2(251, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="24"] +atlas = ExtResource("1") +region = Rect2(1, 440, 36, 36) + +[sub_resource type="AtlasTexture" id="25"] +atlas = ExtResource("1") +region = Rect2(0, 544, 36, 36) + +[sub_resource type="StyleBoxTexture" id="26"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +region_rect = Rect2(321, 440, 49, 49) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 10.0 +content_margin_right = 10.0 +content_margin_bottom = 10.0 +texture = ExtResource("1") +texture_margin_left = 7.94429 +texture_margin_top = 9.53314 +texture_margin_right = 8.73872 +texture_margin_bottom = 13.9025 +expand_margin_top = 10.0 +expand_margin_bottom = 10.0 +region_rect = Rect2(321, 440, 49, 49) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 12.0 +content_margin_top = 10.0 +texture = ExtResource("1") +texture_margin_left = 10.8072 +texture_margin_top = 28.4299 +texture_margin_right = 9.71325 +texture_margin_bottom = 14.7561 +axis_stretch_vertical = 1 +region_rect = Rect2(1, 310, 100, 100) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("1") +region = Rect2(1007, 450, 39, 31) + +[sub_resource type="StyleBoxTexture" id="30"] +texture = ExtResource("1") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(1051, 317, 4, 100) + +[resource] +default_font = SubResource("31") +Button/colors/font_color = Color(0, 0, 0, 1) +Button/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +Button/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +Button/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +Button/constants/h_separation = 2 +Button/fonts/font = SubResource("31") +Button/styles/disabled = SubResource("1") +Button/styles/focus = SubResource("2") +Button/styles/hover = SubResource("3") +Button/styles/normal = SubResource("4") +Button/styles/pressed = SubResource("5") +CheckBox/colors/font_color = Color(0, 0, 0, 1) +CheckBox/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckBox/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_hover_color_pressed = Color(0.34902, 0.34902, 0.34902, 1) +CheckBox/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckBox/constants/check_v_offset = 0 +CheckBox/constants/h_separation = 4 +CheckBox/fonts/font = SubResource("31") +CheckBox/icons/checked = SubResource("6") +CheckBox/icons/radio_checked = SubResource("7") +CheckBox/icons/radio_unchecked = SubResource("8") +CheckBox/icons/unchecked = SubResource("9") +CheckBox/styles/disabled = null +CheckBox/styles/focus = null +CheckBox/styles/hover = SubResource("10") +CheckBox/styles/hover_pressed = SubResource("11") +CheckBox/styles/normal = SubResource("12") +CheckBox/styles/pressed = SubResource("13") +CheckButton/colors/font_color = Color(0, 0, 0, 1) +CheckButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +CheckButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +CheckButton/colors/font_hover_color_pressed = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/colors/font_pressed_color = Color(0.388235, 0.388235, 0.388235, 1) +CheckButton/constants/check_v_offset = 0 +CheckButton/constants/h_separation = 4 +CheckButton/fonts/font = SubResource("31") +CheckButton/icons/checked = SubResource("15") +CheckButton/icons/checked_disabled = SubResource("15") +CheckButton/icons/unchecked = SubResource("14") +CheckButton/icons/unchecked_disabled = SubResource("14") +CheckButton/styles/disabled = null +CheckButton/styles/focus = null +CheckButton/styles/hover = null +CheckButton/styles/hover_pressed = null +CheckButton/styles/normal = null +CheckButton/styles/pressed = null +HSlider/icons/grabber = SubResource("16") +HSlider/icons/grabber_disabled = null +HSlider/icons/grabber_highlight = SubResource("16") +HSlider/icons/tick = null +HSlider/styles/grabber_area = SubResource("17") +HSlider/styles/grabber_area_highlight = SubResource("17") +HSlider/styles/slider = SubResource("17") +LineEdit/colors/clear_button_color = Color(0.88, 0.88, 0.88, 1) +LineEdit/colors/clear_button_color_pressed = Color(1, 1, 1, 1) +LineEdit/colors/cursor_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/colors/font_color = Color(0, 0, 0, 1) +LineEdit/colors/font_color_uneditable = Color(0.88, 0.88, 0.88, 0.5) +LineEdit/colors/font_selected_color = Color(0.894118, 0.894118, 0.894118, 1) +LineEdit/colors/selection_color = Color(0.34902, 0.34902, 0.34902, 1) +LineEdit/constants/minimum_spaces = 12 +LineEdit/fonts/font = SubResource("31") +LineEdit/icons/clear = null +LineEdit/styles/focus = SubResource("18") +LineEdit/styles/normal = SubResource("19") +LineEdit/styles/read_only = SubResource("19") +OptionButton/colors/font_color = Color(0, 0, 0, 1) +OptionButton/colors/font_disabled_color = Color(0.9, 0.9, 0.9, 0.2) +OptionButton/colors/font_hover_color = Color(0.34902, 0.34902, 0.34902, 1) +OptionButton/colors/font_pressed_color = Color(0, 0, 0, 1) +OptionButton/constants/arrow_margin = 2 +OptionButton/constants/h_separation = 2 +OptionButton/fonts/font = SubResource("31") +OptionButton/icons/arrow = SubResource("20") +OptionButton/styles/disabled = null +OptionButton/styles/focus = null +OptionButton/styles/hover = null +OptionButton/styles/normal = null +OptionButton/styles/pressed = null +Panel/styles/panel = SubResource("21") +PopupMenu/colors/font_color = Color(0, 0, 0, 1) +PopupMenu/colors/font_color_accel = Color(0.701961, 0.701961, 0.701961, 0.8) +PopupMenu/colors/font_disabled_color = Color(0.4, 0.4, 0.4, 0.8) +PopupMenu/colors/font_hover_color = Color(0.388235, 0.388235, 0.388235, 1) +PopupMenu/constants/h_separation = 4 +PopupMenu/constants/vseparation = 32 +PopupMenu/fonts/font = SubResource("31") +PopupMenu/icons/checked = SubResource("22") +PopupMenu/icons/radio_checked = SubResource("23") +PopupMenu/icons/radio_unchecked = SubResource("24") +PopupMenu/icons/submenu = null +PopupMenu/icons/unchecked = SubResource("25") +PopupMenu/styles/hover = SubResource("26") +PopupMenu/styles/labeled_separator_left = null +PopupMenu/styles/labeled_separator_right = null +PopupMenu/styles/panel = SubResource("27") +PopupMenu/styles/panel_disabled = null +PopupMenu/styles/separator = null +TextEdit/colors/background_color = Color(0, 0, 0, 0) +TextEdit/colors/bookmark_color = Color(0.08, 0.49, 0.98, 1) +TextEdit/colors/brace_mismatch_color = Color(1, 0.2, 0.2, 1) +TextEdit/colors/breakpoint_color = Color(0.8, 0.8, 0.4, 0.2) +TextEdit/colors/caret_background_color = Color(0, 0, 0, 1) +TextEdit/colors/caret_color = Color(0.88, 0.88, 0.88, 1) +TextEdit/colors/code_folding_color = Color(0.8, 0.8, 0.8, 0.8) +TextEdit/colors/completion_background_color = Color(0.17, 0.16, 0.2, 1) +TextEdit/colors/completion_existing_color = Color(0.87, 0.87, 0.87, 0.13) +TextEdit/colors/completion_font_color = Color(0.67, 0.67, 0.67, 1) +TextEdit/colors/completion_scroll_color = Color(1, 1, 1, 1) +TextEdit/colors/completion_selected_color = Color(0.26, 0.26, 0.27, 1) +TextEdit/colors/current_line_color = Color(0.25, 0.25, 0.26, 0.8) +TextEdit/colors/executing_line_color = Color(0.2, 0.8, 0.2, 0.4) +TextEdit/colors/font_color = Color(0, 0, 0, 1) +TextEdit/colors/font_color_readonly = Color(0.88, 0.88, 0.88, 0.5) +TextEdit/colors/font_selected_color = Color(0.34902, 0.34902, 0.34902, 1) +TextEdit/colors/function_color = Color(0.4, 0.64, 0.81, 1) +TextEdit/colors/line_number_color = Color(0.67, 0.67, 0.67, 0.4) +TextEdit/colors/mark_color = Color(1, 0.4, 0.4, 0.4) +TextEdit/colors/member_variable_color = Color(0.9, 0.31, 0.35, 1) +TextEdit/colors/number_color = Color(0.92, 0.58, 0.2, 1) +TextEdit/colors/safe_line_number_color = Color(0.67, 0.78, 0.67, 0.6) +TextEdit/colors/selection_color = Color(0.49, 0.49, 0.49, 1) +TextEdit/colors/symbol_color = Color(0.94, 0.94, 0.94, 1) +TextEdit/colors/word_highlighted_color = Color(0.8, 0.9, 0.9, 0.15) +TextEdit/constants/completion_lines = 7 +TextEdit/constants/completion_max_width = 50 +TextEdit/constants/completion_scroll_width = 3 +TextEdit/constants/line_spacing = 4 +TextEdit/fonts/font = SubResource("31") +TextEdit/icons/fold = null +TextEdit/icons/folded = null +TextEdit/icons/space = null +TextEdit/icons/tab = null +TextEdit/styles/completion = null +TextEdit/styles/focus = null +TextEdit/styles/normal = SubResource("28") +TextEdit/styles/read_only = null +VSlider/icons/grabber = SubResource("29") +VSlider/icons/grabber_disabled = SubResource("29") +VSlider/icons/grabber_highlight = SubResource("29") +VSlider/icons/tick = null +VSlider/styles/grabber_area = SubResource("30") +VSlider/styles/grabber_area_highlight = SubResource("30") +VSlider/styles/slider = SubResource("30") diff --git a/UI/assets/FlatUI4/kenvector_future.ttf b/UI/assets/FlatUI4/kenvector_future.ttf new file mode 100644 index 00000000..39ebdfaa Binary files /dev/null and b/UI/assets/FlatUI4/kenvector_future.ttf differ diff --git a/UI/assets/FlatUI4/kenvector_future.ttf.import b/UI/assets/FlatUI4/kenvector_future.ttf.import new file mode 100644 index 00000000..d7661c96 --- /dev/null +++ b/UI/assets/FlatUI4/kenvector_future.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://isijx8d2su3s" +path="res://.godot/imported/kenvector_future.ttf-6442148bfe74d04cde9cf828e930d720.fontdata" + +[deps] + +source_file="res://UI/assets/FlatUI4/kenvector_future.ttf" +dest_files=["res://.godot/imported/kenvector_future.ttf-6442148bfe74d04cde9cf828e930d720.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=4 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/UI/assets/neon-wave-theme/neon-wave-theme.png b/UI/assets/neon-wave-theme/neon-wave-theme.png new file mode 100644 index 00000000..ec6f34fe Binary files /dev/null and b/UI/assets/neon-wave-theme/neon-wave-theme.png differ diff --git a/UI/assets/neon-wave-theme/neon-wave-theme.png.import b/UI/assets/neon-wave-theme/neon-wave-theme.png.import new file mode 100644 index 00000000..82f5c98f --- /dev/null +++ b/UI/assets/neon-wave-theme/neon-wave-theme.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6wa5oaqt2vmq" +path="res://.godot/imported/neon-wave-theme.png-2f45b56cede0c0738673f928be5132de.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://UI/assets/neon-wave-theme/neon-wave-theme.png" +dest_files=["res://.godot/imported/neon-wave-theme.png-2f45b56cede0c0738673f928be5132de.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/UI/assets/neon-wave-theme/neon-wave.tres b/UI/assets/neon-wave-theme/neon-wave.tres new file mode 100644 index 00000000..61e205e3 --- /dev/null +++ b/UI/assets/neon-wave-theme/neon-wave.tres @@ -0,0 +1,777 @@ +[gd_resource type="Theme" format=3 uid="uid://u5qh1r677ec2"] + +[ext_resource type="FontFile" uid="uid://bh2gj03hg6v4r" path="res://UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf" id="1"] +[ext_resource type="Texture2D" uid="uid://6wa5oaqt2vmq" path="res://UI/assets/neon-wave-theme/neon-wave-theme.png" id="2"] + +[sub_resource type="StyleBoxTexture" id="8"] +content_margin_top = 7.0 +content_margin_bottom = 7.0 +texture = ExtResource("2") +texture_margin_left = 30.0 +texture_margin_right = 30.0 +region_rect = Rect2(198, 393, 103, 51) + +[sub_resource type="StyleBoxTexture" id="7"] +content_margin_top = 7.0 +content_margin_bottom = 7.0 +texture = ExtResource("2") +texture_margin_left = 30.0 +texture_margin_right = 30.0 +region_rect = Rect2(307, 386, 109, 57) + +[sub_resource type="StyleBoxTexture" id="5"] +content_margin_top = 7.0 +content_margin_bottom = 7.0 +texture = ExtResource("2") +texture_margin_left = 29.7404 +texture_margin_right = 30.0 +expand_margin_left = 3.0 +expand_margin_top = 3.0 +expand_margin_right = 3.0 +expand_margin_bottom = 2.0 +region_rect = Rect2(190, 327, 114, 62) + +[sub_resource type="StyleBoxTexture" id="2"] +content_margin_top = 7.0 +content_margin_bottom = 7.0 +texture = ExtResource("2") +texture_margin_left = 30.0 +texture_margin_right = 30.0 +region_rect = Rect2(77, 329, 110, 58) + +[sub_resource type="StyleBoxTexture" id="6"] +content_margin_top = 7.0 +content_margin_bottom = 7.0 +texture = ExtResource("2") +texture_margin_left = 29.7189 +texture_margin_right = 30.0 +region_rect = Rect2(76, 391, 112, 60) + +[sub_resource type="AtlasTexture" id="12"] +atlas = ExtResource("2") +region = Rect2(549, 378, 36, 39) +margin = Rect2(-5, -5, -10, -10) + +[sub_resource type="AtlasTexture" id="15"] +atlas = ExtResource("2") +region = Rect2(636, 379, 36, 37) +margin = Rect2(-5, -5, -10, -10) + +[sub_resource type="AtlasTexture" id="17"] +atlas = ExtResource("2") +region = Rect2(548, 426, 35, 35) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="AtlasTexture" id="18"] +atlas = ExtResource("2") +region = Rect2(636, 427, 34, 34) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="AtlasTexture" id="19"] +atlas = ExtResource("2") +region = Rect2(509, 427, 33, 33) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="AtlasTexture" id="20"] +atlas = ExtResource("2") +region = Rect2(597, 428, 32, 32) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="AtlasTexture" id="13"] +atlas = ExtResource("2") +region = Rect2(509, 379, 34, 37) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="AtlasTexture" id="16"] +atlas = ExtResource("2") +region = Rect2(596, 380, 34, 35) +margin = Rect2(-3, -3, -5, -5) + +[sub_resource type="StyleBoxEmpty" id="14"] +content_margin_left = 11.0 +content_margin_right = 5.0 + +[sub_resource type="StyleBoxTexture" id="24"] +texture = ExtResource("2") +texture_margin_left = 9.0 +texture_margin_top = 9.0 +texture_margin_right = 9.0 +texture_margin_bottom = 9.0 +region_rect = Rect2(548, 469, 35, 36) + +[sub_resource type="StyleBoxTexture" id="25"] +content_margin_right = 5.0 +texture = ExtResource("2") +texture_margin_left = 9.0 +texture_margin_top = 9.0 +texture_margin_right = 9.0 +texture_margin_bottom = 9.0 +region_rect = Rect2(509, 470, 35, 35) + +[sub_resource type="AtlasTexture" id="39"] +atlas = ExtResource("2") +region = Rect2(598, 508, 60, 36) +margin = Rect2(-3, -3, -6, -6) + +[sub_resource type="AtlasTexture" id="38"] +atlas = ExtResource("2") +region = Rect2(597, 467, 60, 36) +margin = Rect2(-3, -3, -6, -6) + +[sub_resource type="StyleBoxEmpty" id="40"] +content_margin_left = 11.0 +content_margin_right = 5.0 + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_juxe3"] +content_margin_right = 5.0 +content_margin_bottom = 16.0 +texture = ExtResource("2") +texture_margin_left = 9.0 +texture_margin_top = 9.0 +texture_margin_right = 9.0 +texture_margin_bottom = 9.0 +expand_margin_top = 6.0 +expand_margin_bottom = 3.0 +region_rect = Rect2(509, 470, 35, 35) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_k6gwr"] +content_margin_top = 9.0 +texture = ExtResource("2") +texture_margin_left = 9.0 +texture_margin_top = 9.0 +texture_margin_right = 9.0 +texture_margin_bottom = 9.0 +expand_margin_top = 6.0 +expand_margin_bottom = 2.0 +region_rect = Rect2(548, 469, 35, 36) + +[sub_resource type="StyleBoxTexture" id="63"] +texture = ExtResource("2") +texture_margin_left = 5.0 +texture_margin_top = 6.0 +texture_margin_right = 5.0 +texture_margin_bottom = 5.0 +region_rect = Rect2(326, 84, 15, 15) + +[sub_resource type="StyleBoxTexture" id="64"] +texture = ExtResource("2") +texture_margin_left = 5.0 +texture_margin_top = 6.0 +texture_margin_right = 5.0 +texture_margin_bottom = 5.0 +region_rect = Rect2(304, 85, 13, 13) + +[sub_resource type="StyleBoxTexture" id="65"] +texture = ExtResource("2") +texture_margin_left = 5.0 +texture_margin_top = 6.0 +texture_margin_right = 5.0 +texture_margin_bottom = 5.0 +region_rect = Rect2(346, 83, 17, 17) + +[sub_resource type="StyleBoxTexture" id="66"] +content_margin_bottom = 14.0 +texture = ExtResource("2") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(298, 129, 21, 13) + +[sub_resource type="StyleBoxTexture" id="67"] +texture = ExtResource("2") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(298, 144, 21, 12) + +[sub_resource type="StyleBoxTexture" id="30"] +content_margin_top = 4.0 +content_margin_bottom = 4.0 +texture = ExtResource("2") +texture_margin_left = 5.24474 +texture_margin_right = 6.11886 +region_rect = Rect2(214, 459, 46, 9) + +[sub_resource type="AtlasTexture" id="31"] +atlas = ExtResource("2") +region = Rect2(307.208, 458, 19.7922, 19) + +[sub_resource type="AtlasTexture" id="41"] +atlas = ExtResource("2") +region = Rect2(296, 440, 18, 18) + +[sub_resource type="AtlasTexture" id="32"] +atlas = ExtResource("2") +region = Rect2(305, 481, 21, 21) + +[sub_resource type="StyleBoxTexture" id="33"] +texture = ExtResource("2") +texture_margin_left = 9.0 +texture_margin_right = 8.0 +expand_margin_top = 5.0 +expand_margin_bottom = 5.0 +region_rect = Rect2(214, 494, 46, 11) + +[sub_resource type="StyleBoxTexture" id="34"] +texture = ExtResource("2") +texture_margin_left = 8.0 +texture_margin_right = 7.0 +expand_margin_top = 5.0 +expand_margin_bottom = 5.0 +region_rect = Rect2(214, 476, 46, 11) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_rtd58"] +content_margin_left = 20.0 +texture = ExtResource("2") +texture_margin_left = 10.0 +texture_margin_top = 7.0 +texture_margin_right = 8.0 +texture_margin_bottom = 7.0 +region_rect = Rect2(304, 256, 59, 54) + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_227k1"] + +[sub_resource type="StyleBoxTexture" id="54"] +content_margin_left = 20.0 +texture = ExtResource("2") +texture_margin_left = 10.0 +texture_margin_top = 7.0 +texture_margin_right = 8.0 +texture_margin_bottom = 7.0 +region_rect = Rect2(304, 198, 59, 54) + +[sub_resource type="StyleBoxTexture" id="56"] +content_margin_right = 5.0 +content_margin_bottom = 14.0 +texture = ExtResource("2") +texture_margin_left = 11.3722 +texture_margin_top = 12.2943 +texture_margin_right = 11.6796 +texture_margin_bottom = 11.3722 +expand_margin_left = 10.0 +expand_margin_top = 6.0 +expand_margin_bottom = 3.0 +region_rect = Rect2(509, 470, 35, 35) + +[sub_resource type="StyleBoxTexture" id="57"] +content_margin_bottom = 3.0 +texture = ExtResource("2") +texture_margin_left = 11.3722 +texture_margin_top = 12.2943 +texture_margin_right = 11.6796 +texture_margin_bottom = 11.3722 +expand_margin_left = 10.0 +expand_margin_top = 6.0 +expand_margin_bottom = 2.0 +region_rect = Rect2(548, 469, 35, 36) + +[sub_resource type="StyleBoxTexture" id="27"] +content_margin_left = 10.0 +content_margin_top = 8.0 +content_margin_right = 10.0 +texture = ExtResource("2") +texture_margin_left = 3.08429 +texture_margin_top = 5.50548 +texture_margin_right = 2.18471 +texture_margin_bottom = 5.269 +region_rect = Rect2(141, 474, 41, 33) + +[sub_resource type="StyleBoxTexture" id="28"] +content_margin_left = 10.0 +content_margin_top = 8.0 +content_margin_right = 10.0 +texture = ExtResource("2") +texture_margin_left = 3.08429 +texture_margin_top = 5.50548 +texture_margin_right = 2.18471 +texture_margin_bottom = 5.269 +region_rect = Rect2(93, 474, 41, 33) + +[sub_resource type="AtlasTexture" id="47"] +atlas = ExtResource("2") +region = Rect2(683, 445, 18, 13) + +[sub_resource type="StyleBoxTexture" id="4"] +texture = ExtResource("2") +texture_margin_left = 33.0 +texture_margin_top = 35.398 +texture_margin_right = 41.0 +texture_margin_bottom = 33.0 +expand_margin_left = 16.0 +expand_margin_top = 16.0 +expand_margin_right = 16.0 +expand_margin_bottom = 16.0 +region_rect = Rect2(45, 68, 249, 251) + +[sub_resource type="AtlasTexture" id="50"] +atlas = ExtResource("2") +region = Rect2(544, 426, 39, 35) + +[sub_resource type="AtlasTexture" id="51"] +atlas = ExtResource("2") +region = Rect2(504, 427, 38, 33) + +[sub_resource type="AtlasTexture" id="53"] +atlas = ExtResource("2") +region = Rect2(681, 479, 22, 13) + +[sub_resource type="StyleBoxTexture" id="52"] +content_margin_right = 5.0 +content_margin_bottom = 3.0 +texture = ExtResource("2") +texture_margin_left = 11.3722 +texture_margin_top = 12.2943 +texture_margin_right = 11.6796 +texture_margin_bottom = 11.3722 +expand_margin_top = 6.0 +expand_margin_bottom = 2.0 +region_rect = Rect2(548, 469, 35, 36) + +[sub_resource type="StyleBoxTexture" id="48"] +texture = ExtResource("2") +texture_margin_left = 5.0 +texture_margin_right = 7.0 +region_rect = Rect2(215, 459, 43, 8) + +[sub_resource type="StyleBoxTexture" id="49"] +texture = ExtResource("2") +texture_margin_left = 19.0 +texture_margin_top = 20.0 +texture_margin_right = 19.0 +texture_margin_bottom = 19.0 +expand_margin_left = 5.0 +expand_margin_top = 5.0 +expand_margin_right = 5.0 +expand_margin_bottom = 5.0 +region_rect = Rect2(431, 380, 63, 61) + +[sub_resource type="FontFile" id="77"] +fallbacks = Array[Font]([ExtResource("1")]) +cache/0/variation_coordinates = {} +cache/0/face_index = 0 +cache/0/embolden = 0.0 +cache/0/transform = Transform2D(1, 0, 0, 1, 0, 0) +cache/0/spacing_top = 0 +cache/0/spacing_bottom = 0 +cache/0/spacing_space = 0 +cache/0/spacing_glyph = 0 +cache/0/baseline_offset = 0.0 +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/16/0/kerning_overrides/24/0 = Vector2(0, 0) +cache/0/24/0/ascent = 0.0 +cache/0/24/0/descent = 0.0 +cache/0/24/0/underline_position = 0.0 +cache/0/24/0/underline_thickness = 0.0 +cache/0/24/0/scale = 1.0 +cache/0/24/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/24/0/kerning_overrides/24/0 = Vector2(0, 0) +cache/0/14/0/ascent = 0.0 +cache/0/14/0/descent = 0.0 +cache/0/14/0/underline_position = 0.0 +cache/0/14/0/underline_thickness = 0.0 +cache/0/14/0/scale = 1.0 + +[sub_resource type="StyleBoxTexture" id="78"] +texture = ExtResource("2") +texture_margin_left = 12.0 +texture_margin_top = 10.0 +texture_margin_right = 9.0 +texture_margin_bottom = 11.0 +expand_margin_left = 8.0 +expand_margin_top = 8.0 +expand_margin_right = 8.0 +expand_margin_bottom = 8.0 +region_rect = Rect2(509, 380, 33, 35) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_tht2w"] +texture = ExtResource("2") +texture_margin_top = 4.0 +texture_margin_bottom = 5.0 +axis_stretch_horizontal = 1 +region_rect = Rect2(208, 518, 9, 19) + +[sub_resource type="AtlasTexture" id="29"] +atlas = ExtResource("2") +region = Rect2(682, 429, 20, 29) + +[sub_resource type="StyleBoxTexture" id="43"] +texture = ExtResource("2") +texture_margin_left = 45.5754 +texture_margin_top = 18.6888 +texture_margin_right = 45.7291 +texture_margin_bottom = 21.3529 +expand_margin_top = 8.0 +region_rect = Rect2(430.621, 379.193, 65.0473, 64.7067) + +[sub_resource type="StyleBoxTexture" id="46"] +texture = ExtResource("2") +texture_margin_left = 14.2304 +texture_margin_top = 14.0 +texture_margin_right = 14.3841 +texture_margin_bottom = 10.0 +region_rect = Rect2(424.273, 537.814, 55.4779, 36.293) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_pdxsw"] +texture = ExtResource("2") +texture_margin_left = 18.3912 +texture_margin_top = 10.0 +texture_margin_right = 20.2093 +texture_margin_bottom = 10.0 +region_rect = Rect2(344.385, 535.872, 62.4127, 34.119) + +[sub_resource type="StyleBoxTexture" id="44"] +texture = ExtResource("2") +texture_margin_left = 45.5754 +texture_margin_top = 10.0 +texture_margin_right = 45.7291 +texture_margin_bottom = 10.0 +expand_margin_bottom = 11.0 +region_rect = Rect2(339.847, 475.303, 72.8501, 43.8102) + +[sub_resource type="StyleBoxTexture" id="45"] +texture = ExtResource("2") +texture_margin_left = 18.3912 +texture_margin_top = 10.0 +texture_margin_right = 20.2093 +texture_margin_bottom = 10.0 +region_rect = Rect2(344.385, 535.872, 62.4127, 34.119) + +[sub_resource type="StyleBoxTexture" id="22"] +texture = ExtResource("2") +texture_margin_left = 20.2199 +texture_margin_top = 20.3803 +texture_margin_right = 20.3848 +texture_margin_bottom = 20.8481 +region_rect = Rect2(429, 462, 67, 67) + +[sub_resource type="StyleBoxTexture" id="23"] +texture = ExtResource("2") +texture_margin_left = 21.0725 +texture_margin_top = 20.4596 +texture_margin_right = 20.3848 +texture_margin_bottom = 20.8481 +region_rect = Rect2(429, 377, 67, 67) + +[sub_resource type="AtlasTexture" id="69"] +atlas = ExtResource("2") +region = Rect2(681, 469, 12, 10) + +[sub_resource type="AtlasTexture" id="70"] +atlas = ExtResource("2") +region = Rect2(682, 480, 10, 10) + +[sub_resource type="AtlasTexture" id="71"] +atlas = ExtResource("2") +region = Rect2(548, 378, 38, 38) + +[sub_resource type="AtlasTexture" id="72"] +atlas = ExtResource("2") +region = Rect2(508, 379, 36, 36) + +[sub_resource type="AtlasTexture" id="73"] +atlas = ExtResource("2") +region = Rect2(681, 429, 22, 30) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_18g31"] +texture = ExtResource("2") +texture_margin_left = 6.0 +texture_margin_top = 6.0 +texture_margin_right = 6.0 +texture_margin_bottom = 5.0 +region_rect = Rect2(307, 314, 58, 54) + +[sub_resource type="StyleBoxTexture" id="68"] +texture = ExtResource("2") +texture_margin_left = 6.0 +texture_margin_top = 6.0 +texture_margin_right = 6.0 +texture_margin_bottom = 5.0 +region_rect = Rect2(304, 198, 59, 54) + +[sub_resource type="StyleBoxTexture" id="75"] +content_margin_right = 5.0 +content_margin_bottom = 14.0 +texture = ExtResource("2") +texture_margin_left = 11.3722 +texture_margin_top = 12.2943 +texture_margin_right = 11.6796 +texture_margin_bottom = 11.3722 +expand_margin_left = 18.0 +expand_margin_top = 6.0 +expand_margin_bottom = 3.0 +region_rect = Rect2(509, 470, 35, 35) + +[sub_resource type="StyleBoxTexture" id="76"] +content_margin_right = 5.0 +content_margin_bottom = 14.0 +texture = ExtResource("2") +texture_margin_left = 11.3722 +texture_margin_top = 12.2943 +texture_margin_right = 11.6796 +texture_margin_bottom = 11.3722 +expand_margin_left = 18.0 +expand_margin_top = 6.0 +expand_margin_bottom = 3.0 +region_rect = Rect2(548, 470, 35, 35) + +[sub_resource type="StyleBoxTexture" id="58"] +texture = ExtResource("2") +texture_margin_left = 7.0 +texture_margin_top = 7.0 +texture_margin_right = 7.0 +texture_margin_bottom = 7.0 +region_rect = Rect2(324, 103, 19, 19) + +[sub_resource type="StyleBoxTexture" id="59"] +texture = ExtResource("2") +texture_margin_left = 7.0 +texture_margin_top = 7.0 +texture_margin_right = 7.0 +texture_margin_bottom = 7.0 +region_rect = Rect2(303, 105, 15, 15) + +[sub_resource type="StyleBoxTexture" id="60"] +texture = ExtResource("2") +texture_margin_left = 7.0 +texture_margin_top = 7.0 +texture_margin_right = 7.0 +texture_margin_bottom = 7.0 +region_rect = Rect2(345, 103, 19, 19) + +[sub_resource type="StyleBoxTexture" id="61"] +content_margin_right = 18.0 +texture = ExtResource("2") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 2.0 +region_rect = Rect2(327, 129, 13, 53) + +[sub_resource type="StyleBoxTexture" id="62"] +texture = ExtResource("2") +texture_margin_left = 1.0 +texture_margin_top = 1.0 +texture_margin_right = 1.0 +texture_margin_bottom = 1.0 +region_rect = Rect2(348, 129, 13, 52) + +[sub_resource type="StyleBoxTexture" id="26"] +texture = ExtResource("2") +texture_margin_top = 6.79237 +texture_margin_bottom = 6.03766 +region_rect = Rect2(197, 459, 9, 46) + +[sub_resource type="AtlasTexture" id="42"] +atlas = ExtResource("2") +region = Rect2(297, 441, 17, 16) + +[sub_resource type="AtlasTexture" id="35"] +atlas = ExtResource("2") +region = Rect2(305, 480, 21, 23) + +[sub_resource type="StyleBoxTexture" id="36"] +texture = ExtResource("2") +texture_margin_top = 8.36241 +texture_margin_bottom = 9.0 +expand_margin_left = 4.0 +expand_margin_right = 6.0 +region_rect = Rect2(285.208, 458, 10.7925, 47) + +[sub_resource type="StyleBoxTexture" id="37"] +texture = ExtResource("2") +texture_margin_top = 7.04203 +texture_margin_bottom = 7.04203 +expand_margin_left = 4.0 +expand_margin_right = 6.0 +region_rect = Rect2(267, 460, 12, 45) + +[sub_resource type="AtlasTexture" id="AtlasTexture_tuhtt"] +atlas = ExtResource("2") +region = Rect2(45, 336, 27, 25) + +[sub_resource type="AtlasTexture" id="AtlasTexture_0glbv"] +atlas = ExtResource("2") +region = Rect2(45, 336, 27, 25) + +[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_xh6cw"] +texture = ExtResource("2") +texture_margin_left = 17.647 +texture_margin_top = 57.3692 +texture_margin_right = 22.5686 +texture_margin_bottom = 17.0167 +expand_margin_left = 12.0 +expand_margin_top = 52.0 +expand_margin_right = 12.0 +expand_margin_bottom = 13.0 +region_rect = Rect2(367, 39, 283, 303) + +[sub_resource type="FontFile" id="9"] +fallbacks = Array[Font]([ExtResource("1")]) +cache/0/variation_coordinates = {} +cache/0/face_index = 0 +cache/0/embolden = 0.0 +cache/0/transform = Transform2D(1, 0, 0, 1, 0, 0) +cache/0/spacing_top = 0 +cache/0/spacing_bottom = 0 +cache/0/spacing_space = 0 +cache/0/spacing_glyph = 0 +cache/0/baseline_offset = 0.0 +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/16/0/kerning_overrides/24/0 = Vector2(0, 0) +cache/0/24/0/ascent = 0.0 +cache/0/24/0/descent = 0.0 +cache/0/24/0/underline_position = 0.0 +cache/0/24/0/underline_thickness = 0.0 +cache/0/24/0/scale = 1.0 +cache/0/24/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/24/0/kerning_overrides/24/0 = Vector2(0, 0) +cache/0/14/0/ascent = 0.0 +cache/0/14/0/descent = 0.0 +cache/0/14/0/underline_position = 0.0 +cache/0/14/0/underline_thickness = 0.0 +cache/0/14/0/scale = 1.0 + +[resource] +default_font = SubResource("9") +default_font_size = 16 +Button/colors/font_color = Color(0.752941, 0.992157, 0.988235, 1) +Button/colors/font_color_hover = Color(0.443137, 1, 0.941176, 1) +Button/colors/font_color_pressed = Color(0.180392, 0.0666667, 0.572549, 1) +Button/styles/disabled = SubResource("8") +Button/styles/focus = SubResource("7") +Button/styles/hover = SubResource("5") +Button/styles/normal = SubResource("2") +Button/styles/pressed = SubResource("6") +CheckBox/colors/font_color_focus = Color(0.694118, 0.921569, 1, 1) +CheckBox/colors/font_color_pressed = Color(0.972549, 0.941176, 0.682353, 1) +CheckBox/icons/checked = SubResource("12") +CheckBox/icons/checked_disabled = SubResource("15") +CheckBox/icons/radio_checked = SubResource("17") +CheckBox/icons/radio_checked_disabled = SubResource("18") +CheckBox/icons/radio_unchecked = SubResource("19") +CheckBox/icons/radio_unchecked_disabled = SubResource("20") +CheckBox/icons/unchecked = SubResource("13") +CheckBox/icons/unchecked_disabled = SubResource("16") +CheckBox/styles/disabled = SubResource("14") +CheckBox/styles/focus = SubResource("24") +CheckBox/styles/hover = SubResource("25") +CheckBox/styles/hover_pressed = SubResource("24") +CheckBox/styles/normal = SubResource("14") +CheckBox/styles/pressed = SubResource("14") +CheckButton/colors/font_color_focus = Color(0.694118, 0.921569, 1, 1) +CheckButton/colors/font_color_pressed = Color(0.972549, 0.941176, 0.682353, 1) +CheckButton/constants/check_vadjust = -2 +CheckButton/icons/checked = SubResource("39") +CheckButton/icons/checked_disabled = SubResource("39") +CheckButton/icons/unchecked = SubResource("38") +CheckButton/icons/unchecked_disabled = SubResource("38") +CheckButton/styles/disabled = SubResource("40") +CheckButton/styles/focus = SubResource("24") +CheckButton/styles/hover = SubResource("StyleBoxTexture_juxe3") +CheckButton/styles/hover_pressed = SubResource("StyleBoxTexture_k6gwr") +CheckButton/styles/normal = SubResource("14") +CheckButton/styles/pressed = SubResource("14") +HScrollBar/styles/grabber = SubResource("63") +HScrollBar/styles/grabber_highlight = SubResource("64") +HScrollBar/styles/grabber_pressed = SubResource("65") +HScrollBar/styles/scroll = SubResource("66") +HScrollBar/styles/scroll_focus = SubResource("67") +HSeparator/styles/separator = SubResource("30") +HSlider/icons/grabber = SubResource("31") +HSlider/icons/grabber_disabled = SubResource("41") +HSlider/icons/grabber_highlight = SubResource("32") +HSlider/styles/grabber_area = SubResource("33") +HSlider/styles/grabber_area_highlight = SubResource("33") +HSlider/styles/slider = SubResource("34") +ItemList/colors/font_color = Color(0.694118, 0.921569, 1, 1) +ItemList/colors/font_color_selected = Color(0.972549, 0.941176, 0.682353, 1) +ItemList/styles/cursor = SubResource("25") +ItemList/styles/cursor_unfocused = SubResource("25") +ItemList/styles/focus = SubResource("StyleBoxTexture_rtd58") +ItemList/styles/hovered = SubResource("StyleBoxEmpty_227k1") +ItemList/styles/panel = SubResource("54") +ItemList/styles/selected = SubResource("56") +ItemList/styles/selected_focus = SubResource("57") +Label/colors/font_color = Color(0.980392, 0.803922, 0.988235, 1) +LineEdit/colors/cursor_color = Color(0.694118, 0.921569, 1, 1) +LineEdit/colors/font_color = Color(0.980392, 0.803922, 0.988235, 1) +LineEdit/colors/selection_color = Color(0.694118, 0.921569, 1, 1) +LineEdit/styles/focus = SubResource("27") +LineEdit/styles/normal = SubResource("28") +OptionButton/constants/arrow_margin = 16 +OptionButton/icons/arrow = SubResource("47") +Panel/styles/panel = SubResource("4") +PopupMenu/icons/checked = SubResource("12") +PopupMenu/icons/radio_checked = SubResource("50") +PopupMenu/icons/radio_unchecked = SubResource("51") +PopupMenu/icons/submenu = SubResource("53") +PopupMenu/icons/unchecked = SubResource("13") +PopupMenu/styles/hover = SubResource("52") +PopupMenu/styles/labeled_separator_left = SubResource("48") +PopupMenu/styles/labeled_separator_right = SubResource("48") +PopupMenu/styles/panel = SubResource("49") +PopupMenu/styles/panel_disabled = SubResource("49") +PopupMenu/styles/separator = SubResource("48") +PopupPanel/styles/panel = SubResource("49") +ProgressBar/colors/font_color = Color(0.694118, 0.921569, 1, 1) +ProgressBar/colors/font_outline_color = Color(0, 0, 0, 1) +ProgressBar/constants/outline_size = 15 +ProgressBar/fonts/font = SubResource("77") +ProgressBar/styles/background = SubResource("78") +ProgressBar/styles/fill = SubResource("StyleBoxTexture_tht2w") +SpinBox/icons/updown = SubResource("29") +TabContainer/styles/panel = SubResource("43") +TabContainer/styles/tab_disabled = SubResource("46") +TabContainer/styles/tab_hovered = SubResource("StyleBoxTexture_pdxsw") +TabContainer/styles/tab_selected = SubResource("44") +TabContainer/styles/tab_unselected = SubResource("45") +TextEdit/colors/caret_color = Color(0.443137, 1, 0.941176, 1) +TextEdit/colors/selection_color = Color(0.34902, 0.666667, 0.819608, 1) +TextEdit/styles/focus = SubResource("22") +TextEdit/styles/normal = SubResource("23") +Tree/colors/children_hl_line_color = Color(0.694118, 0.921569, 1, 1) +Tree/colors/font_color = Color(0.694118, 0.921569, 1, 1) +Tree/colors/font_color_selected = Color(0.972549, 0.941176, 0.682353, 1) +Tree/colors/parent_hl_line_color = Color(0.972549, 0.941176, 0.682353, 1) +Tree/colors/relationship_line_color = Color(0.54902, 0.968627, 0.956863, 1) +Tree/constants/draw_relationship_lines = 1 +Tree/icons/arrow = SubResource("69") +Tree/icons/arrow_collapsed = SubResource("70") +Tree/icons/checked = SubResource("71") +Tree/icons/select_arrow = SubResource("69") +Tree/icons/unchecked = SubResource("72") +Tree/icons/updown = SubResource("73") +Tree/styles/focus = SubResource("StyleBoxTexture_18g31") +Tree/styles/panel = SubResource("68") +Tree/styles/selected = SubResource("75") +Tree/styles/selected_focus = SubResource("76") +VScrollBar/styles/grabber = SubResource("58") +VScrollBar/styles/grabber_highlight = SubResource("59") +VScrollBar/styles/grabber_pressed = SubResource("60") +VScrollBar/styles/scroll = SubResource("61") +VScrollBar/styles/scroll_focus = SubResource("62") +VSeparator/styles/separator = SubResource("26") +VSlider/icons/grabber = SubResource("31") +VSlider/icons/grabber_disabled = SubResource("42") +VSlider/icons/grabber_highlight = SubResource("35") +VSlider/styles/grabber_area = SubResource("36") +VSlider/styles/grabber_area_highlight = SubResource("36") +VSlider/styles/slider = SubResource("37") +Window/constants/close_h_offset = 32 +Window/constants/close_v_offset = 40 +Window/constants/title_height = 48 +Window/icons/close = SubResource("AtlasTexture_tuhtt") +Window/icons/close_pressed = SubResource("AtlasTexture_0glbv") +Window/styles/embedded_border = SubResource("StyleBoxTexture_xh6cw") diff --git a/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf b/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf new file mode 100644 index 00000000..993e80a3 Binary files /dev/null and b/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf differ diff --git a/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf.import b/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf.import new file mode 100644 index 00000000..11943136 --- /dev/null +++ b/UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bh2gj03hg6v4r" +path="res://.godot/imported/Polentical Neon Regular.ttf-4bc2a9b691e887e5910ecc65360998e0.fontdata" + +[deps] + +source_file="res://UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Regular.ttf" +dest_files=["res://.godot/imported/Polentical Neon Regular.ttf-4bc2a9b691e887e5910ecc65360998e0.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=1 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/gdata_orm/condition.gd b/addons/gdata_orm/condition.gd new file mode 100644 index 00000000..c9669c2e --- /dev/null +++ b/addons/gdata_orm/condition.gd @@ -0,0 +1,196 @@ +@tool +extends RefCounted +class_name Condition +## A programatically way to define Conditions. +## +## Condition is a way to define SQL Statements to be used with GDataORM. You can create +## full SQL Statements, or Simple conditions to be used when fetching data from the [SQLite] +## database.[br][br] +## +## [b]Example:[/b] +## [codeblock] +## var fetch_like_mar: Condition = Condition.new().select("*").from("my_table").where() \ +## .like("name", "Mar%") +## var fetch_gold: Condition = Condition.new().select(["gold"]).from("inventories").where() \ +## .greater("gold",0) +## var low_health: Condition = Condition.new().lesser("health",5) +## var mid_health: Condition = Condition.new().between("health",25,75) +## var full_health: Condition = Condition.new().greater_equal("health",100) +## [/codeblock] + +enum _CT { + NOT, + EQUAL, + LESS_THAN, + GREATER_THAN, + LESS_THAN_EQUAL, + GREATER_THAN_EQUAL, + AND, + BETWEEN, + IN, + LIKE, + OR, + SELECT, + FROM, + WHERE, +} + +var _conditions: Array = [] + +func _single_op(op: _CT) -> Dictionary: + return {"type": op} + +func _param_op(op: _CT, param: Variant) -> Dictionary: + return {"type": op, "param": param} + +func _comparison_op(op: _CT, column: Variant, value: Variant) -> Dictionary: + return {"type": op, "column": column, "value": value} + +## Binary operator to invert true and false in a statement. +func is_not() -> Condition: + _conditions.append(_single_op(_CT.NOT)) + return self + +## Evaluates the equality of a [param column] value and the [param value] given. +func equal(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.EQUAL, column, value)) + return self + +## Evaluates the [param column] value to be lesser than [param value] given. +func lesser(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.LESS_THAN, column, value)) + return self + +## Evaluates the [param column] value to be greater than [param value] given. +func greater(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.GREATER_THAN, column, value)) + return self + +## Evaluates the [param column] value to be lesser than or equal to [param value] given. +func lesser_equal(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.LESS_THAN_EQUAL, column, value)) + return self + +## Evaluates the [param column] value to be greater than or equal to [param value] given. +func greater_equal(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.GREATER_THAN_EQUAL, column, value)) + return self + +## Binary operator for AND'ing two evaluation values together. +func also() -> Condition: + _conditions.append(_single_op(_CT.AND)) + return self + +## Evaluates the [param column] value to be between [param lower]'s value and [param upper]'s value. +func between(column: String, lower: Variant, upper: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.BETWEEN, column, [lower, upper])) + return self + +## Evaluates the [param column] to see if [param value] is included in it. You can pass an array +## of values to this, or use a [Condition] to fetch data from another table. +func includes(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.IN, column, value)) + return self + +## Evaluates the [param column] to see if [param value] matches the given string. This utilizes +## [SQLite]'s LIKE statement, which means that you can use wildcard operators '%' and '_' in the +## pattern string.[br][br] +## [b]Wildcard Patterns:[/b][br] +## - '%' match any 1 or more characters in a pattern, EG: "Mar%" will return "Mario", "Mark", "Margin" etc etc.[br] +## - '_' match only 1 wildcard character before moving to the next. EG: "h_nt" will return "hunt", "hint", or +## "__pple" will return "topple", "supple", "tipple"[br] +## [b][color=red]NOTE:[/color][/b] [SQLite]'s engine is case-insensitive, so [code]"A" LIKE "a"[/code] will return true, but +## unicode characters that are not in the ASCII range are case-sensitive, so [code]"Ä" LIKE "ä"[/code] will return false. +func like(column: String, value: Variant) -> Condition: + _conditions.append(_comparison_op(_CT.LIKE, column, value)) + return self + +## Binary operator for OR'ing two evaluations together. +func otherwise() -> Condition: + _conditions.append(_single_op(_CT.OR)) + return self + +## Statement, fetches [param columns] from a table during execution. [param columns] can be a string value of "*" or "column1, column2, column3" or an +## array of strings such as ["column1","column2","column3"]. +func select(columns: Variant) -> Condition: + _conditions.append(_param_op(_CT.SELECT, columns)) + return self + +## Statement Modifier, used in conjunction with [method Condition.select] to define which table the data is to be fetched from. +func from(table: String) -> Condition: + _conditions.append(_param_op(_CT.FROM, table)) + return self + +## Statement Modifier, defines the conditions that must match in order to fetch data from the table. +func where() -> Condition: + _conditions.append(_single_op(_CT.WHERE)) + return self + +func _to_string() -> String: + var str = "" + var pos := 0 + + for cond in _conditions: + match cond.type: + #NOTE Single Operation _single_op() + _CT.NOT: + if _conditions[pos+1].type == _CT.BETWEEN: + pos += 1 + continue + str += "NOT " + _CT.AND: + str += "AND " + _CT.OR: + str += "OR " + _CT.WHERE: + str += "WHERE " + + #NOTE Param Operation _param_op() + _CT.SELECT: + var param = "" + if cond.param is Array: + param = ", ".join(cond.param) + elif cond.param is String: + param = cond.param + else: + assert(false, "SELECT statement only takes a String or Array parameters.") + str += "SELECT %s " % param + _CT.FROM: + str += "FROM %s " % cond.param + + #NOTE Comparison Operation _comparison_op() + _CT.EQUAL: + if typeof(cond.value) == TYPE_STRING: + str += "%s = '%s'" % [cond.column, cond.value] + else: + str += "%s = %s " % [cond.column, cond.value] + _CT.LESS_THAN: + str += "%s < %s " % [cond.column, cond.value] + _CT.GREATER_THAN: + str += "%s > %s " % [cond.column, cond.value] + _CT.LESS_THAN_EQUAL: + str += "%s <= %s " % [cond.column, cond.value] + _CT.GREATER_THAN_EQUAL: + str += "%s >= %s " % [cond.column, cond.value] + _CT.BETWEEN: + if _conditions[pos-1].type == _CT.NOT: + str += "%s NOT BETWEEN " % cond.column + else: + str += "%s BETWEEN " % cond.column + str += "%s and %s" % cond.value + _CT.IN: + if _conditions[pos-1].type == _CT.NOT: + str += "%s NOT IN " % cond.column + else: + str += "%s IN " % cond.column + if cond.value is Condition: + str += "(%s) " % cond.value.to_string() + elif cond.value is Array: + str += "(%s) " % ", ".join(cond.value) + else: + assert(false, "IN only takes Array of values or a Condition") + _CT.LIKE: + str += "%s LIKE '%s' " % [cond.column, cond.value] + pos += 1 + + return str.strip_edges() diff --git a/addons/gdata_orm/condition.gd.uid b/addons/gdata_orm/condition.gd.uid new file mode 100644 index 00000000..bf91c59c --- /dev/null +++ b/addons/gdata_orm/condition.gd.uid @@ -0,0 +1 @@ +uid://b3fmxmewptm3k diff --git a/addons/gdata_orm/context.gd b/addons/gdata_orm/context.gd new file mode 100644 index 00000000..22022ffd --- /dev/null +++ b/addons/gdata_orm/context.gd @@ -0,0 +1,86 @@ +extends RefCounted +class_name Context +## The Central core for interacting with Databases. +## +## Context is the class which creates a Database Connection to a [SQLite] database, +## and sets up all tables as defined by the DbSet definitions of the class.[br][br] +## +## [b]Example:[/b] +## [codeblock] +## extends Context +## class_name AppContext +## +## var characters: DbSet +## var inventories: DbSet +## var items: DbSet +## +## func _init() -> void: +## characters = DbSet.new(Character) +## inventories = DbSet.new(Inventory) +## items = DbSet.new(Item) +## +## [/codeblock] + +## The path in which to find the database, Example: `my_context.file_path = "user://my_database.db"` +@export var file_path: String + +var _db: SQLite + +## Called to initialize the Context. This should be called just after the creation of the context, in order to +## register all [DbSet]'s with the Context, as well, as call [method SQLiteObject.setup] on all SQLiteObject's +## that have been defined as a DbSet of this class. +func setup() -> void: + var props = get_property_list() + for prop in props: + if not prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + continue + if prop.type != TYPE_OBJECT or prop.class_name != "DbSet": + continue + + var dbset: DbSet = get(prop.name) + dbset._klass.setup(dbset._klass) + +## Opens the [SQLite] database, allowing for the usages of the [DbSet]s defined in this context. +func open_db(db_path: String = "") -> void: + var props = get_property_list() + if db_path != "": + file_path = db_path + _db = SQLite.new() + _db.path = file_path + _db.open_db() + for prop in props: + if not prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + continue + if prop.type != TYPE_OBJECT or prop.class_name != "DbSet": + continue + var dbset: DbSet = get(prop.name) + dbset.set_db(_db) + +## Closes the database when finished interacting with this instance. +func close_db() -> void: + _db.close_db() + + +## Ensures that all tables defined as [DbSet] in this context, are properly created in the database. +func ensure_tables() -> void: + var props = get_property_list() + for prop in props: + if not prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + continue + if prop.type != TYPE_OBJECT or prop.class_name != "DbSet": + continue + var dbset: DbSet = get(prop.name) + if not dbset.table_exists(): + dbset.create_table(false) + +## Forces the creation of all tables defined as [DbSet] in this context, are properly created, dropping +## the table if it already exists. +func force_create_tables() -> void: + var props = get_property_list() + for prop in props: + if not prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + continue + if prop.type != TYPE_OBJECT or prop.class_name != "DbSet": + continue + var dbset: DbSet = get(prop.name) + dbset.create_table(true) diff --git a/addons/gdata_orm/context.gd.uid b/addons/gdata_orm/context.gd.uid new file mode 100644 index 00000000..34df299c --- /dev/null +++ b/addons/gdata_orm/context.gd.uid @@ -0,0 +1 @@ +uid://baaxskalxregg diff --git a/addons/gdata_orm/db_set.gd b/addons/gdata_orm/db_set.gd new file mode 100644 index 00000000..87a9ef87 --- /dev/null +++ b/addons/gdata_orm/db_set.gd @@ -0,0 +1,86 @@ +@tool +extends Resource +class_name DbSet +## A Central point for fetching/storing [SQLiteObject]s in a Database. +## +## DbSet is used to create a link between [SQLiteObject]s and the tables they are stored in. Functions for +## inserting, fetching, and removing [SQLiteObject]s in a database, along with support functions for checking +## if a table exists, and creating tables.[br][br] +## +## [b]Example:[/b] +## [codeblock] +## extends Context +## class_name AppContext +## +## var characters: DbSet +## var inventories: DbSet +## var items: DbSet +## +## func _init() -> void: +## characters = DbSet.new(Character) +## inventories = DbSet.new(Inventory) +## items = DbSet.new(Item) +## +## [/codeblock] + +var _klass: GDScript +var _db: SQLite + +## Create an instance of DbSet, specifying the [SQLiteObject] inherited class that represents +## the table that this set should interact with. +func _init(h_klass: GDScript) -> void: + _klass = h_klass + +## Set the [SQLite] database handle for this [DbSet]. This is handled internally by [method Context.setup], +## but you can also set a custom database handle for this DbSet. +func set_db(db_conn: SQLite) -> void: + _db = db_conn + +## Creates the backing table for the [SQLiteObject] inherited backing class for the object. +func create_table(drop_if_exists: bool) -> void: + SQLiteObject._create_table(_db, _klass, drop_if_exists) + +## Check to see if the table exists or not. +func table_exists() -> bool: + return SQLiteObject._table_exists(_db, _klass) + +## Check's the [SQLite] database to see if the ID exists in the database. Require's [method SQLiteObject.set_column_flags] +## being called assigning a column as a Primary Key. +func has_id(id: Variant) -> bool: + return SQLiteObject._has_id(_db, _klass, id) + +## Searches the [SQLite] database for an object that matches the [Condition] as given. Returns the +## [SQLiteObject] instance if found, otherwise returns null if it found nothing. +func find_one(conditions: Condition) -> SQLiteObject: + return SQLiteObject._find_one(_db, _klass, conditions) + +## Searches the [SQLite] database for any matching object that matches the [Condition] given. Returns +## an Array of [SQLiteObject]s that was found, otherwise returns an Empty array if nothing is found. +func find_many(conditions: Condition) -> Array: + return SQLiteObject._find_many(_db, _klass, conditions) + +## Returns all saved [SQLiteObject]s stored in the [SQLite] database. +func all() -> Array: + return SQLiteObject._all(_db, _klass) + +## Stores a [SQLiteObject] in the [SQLite] database. Until this is called, an [SQLiteObject] is not +## saved in the database, and [method SQLiteObject.save] will not work. Using this method will +## automatically save the data to the Database when executed.[br][br] +## [b][color=red]NOTE:[/color][/b] [SQLiteObject]s defined with an Auto-Increment Primary key, will +## set their primary key variable once this method is run automatically for you. If the primary +## key is not set after calling this method, then it was not saved to the database. +func append(obj: SQLiteObject) -> void: + assert(obj.get_script() == _klass, "Attempting to add an SQLiteObject of %s to table of type %s!" % + [obj.get_script().get_global_name(), _klass.get_global_name()] + ) + obj._db = _db + obj.save() + +## Removes a [SQLiteObject] from the [SQLite] database. This function calls [method SQLiteObject.delete] +## function to remove it from the database. You can use either method to remove the object from the database. +func erase(obj: SQLiteObject) -> void: + assert(obj.get_script() == _klass, "Attempting to remove an SQLiteObject of %s to table of type %s!" % + [obj.get_script().get_global_name(), _klass.get_global_name()] + ) + obj._db = _db + obj.delete() diff --git a/addons/gdata_orm/db_set.gd.uid b/addons/gdata_orm/db_set.gd.uid new file mode 100644 index 00000000..196f4bc3 --- /dev/null +++ b/addons/gdata_orm/db_set.gd.uid @@ -0,0 +1 @@ +uid://c2buvo3nsojg diff --git a/addons/gdata_orm/godot-sqlite/LICENSE.md b/addons/gdata_orm/godot-sqlite/LICENSE.md new file mode 100644 index 00000000..68d6eb5f --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2024 Piet Bronders & Jeroen De Geeter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.arm64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.arm64.so new file mode 100644 index 00000000..728daa29 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.arm64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.x86_64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.x86_64.so new file mode 100644 index 00000000..d3866b9d Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_debug.x86_64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.arm64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.arm64.so new file mode 100644 index 00000000..ab4b36aa Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.arm64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.x86_64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.x86_64.so new file mode 100644 index 00000000..326bb4e6 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.android.template_release.x86_64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.arm64.dylib b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.arm64.dylib new file mode 100644 index 00000000..2110e9ef Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.arm64.dylib differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/Info.plist new file mode 100644 index 00000000..31fa2a96 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + libgdsqlite.ios.template_debug.a + LibraryIdentifier + ios-arm64 + LibraryPath + libgdsqlite.ios.template_debug.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libgdsqlite.ios.template_debug.simulator.a + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgdsqlite.ios.template_debug.simulator.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64/libgdsqlite.ios.template_debug.a b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64/libgdsqlite.ios.template_debug.a new file mode 100644 index 00000000..1f73c449 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64/libgdsqlite.ios.template_debug.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_debug.simulator.a b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_debug.simulator.a new file mode 100644 index 00000000..ea105313 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_debug.simulator.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.arm64.dylib b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.arm64.dylib new file mode 100644 index 00000000..07641a68 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.arm64.dylib differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/Info.plist new file mode 100644 index 00000000..c75d2c21 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + libgdsqlite.ios.template_release.a + LibraryIdentifier + ios-arm64 + LibraryPath + libgdsqlite.ios.template_release.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libgdsqlite.ios.template_release.simulator.a + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgdsqlite.ios.template_release.simulator.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64/libgdsqlite.ios.template_release.a b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64/libgdsqlite.ios.template_release.a new file mode 100644 index 00000000..b7925bd0 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64/libgdsqlite.ios.template_release.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_release.simulator.a b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_release.simulator.a new file mode 100644 index 00000000..5032da13 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgdsqlite.ios.template_release.simulator.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_debug.x86_64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_debug.x86_64.so new file mode 100644 index 00000000..040ef578 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_debug.x86_64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_release.x86_64.so b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_release.x86_64.so new file mode 100644 index 00000000..361fb3a9 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.linux.template_release.x86_64.so differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/Resources/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/Resources/Info.plist new file mode 100644 index 00000000..09949050 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/Resources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleExecutable + libgdsqlite.template_debug + CFBundleIdentifier + org.godotengine.libgdsqlite + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + libgdsqlite.macos.template_debug + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0.0 + LSMinimumSystemVersion + 10.12 + + \ No newline at end of file diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libgdsqlite.macos.template_debug b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libgdsqlite.macos.template_debug new file mode 100644 index 00000000..5a2c5478 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libgdsqlite.macos.template_debug differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libmacos.libgdsqlite.template_debug b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libmacos.libgdsqlite.template_debug new file mode 100644 index 00000000..51d2de34 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_debug.framework/libmacos.libgdsqlite.template_debug differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/Resources/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/Resources/Info.plist new file mode 100644 index 00000000..59a03997 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/Resources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleExecutable + libgdsqlite.template_release + CFBundleIdentifier + org.godotengine.libgdsqlite + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + libgdsqlite.macos.template_release + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0.0 + LSMinimumSystemVersion + 10.12 + + \ No newline at end of file diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libgdsqlite.macos.template_release b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libgdsqlite.macos.template_release new file mode 100644 index 00000000..d5867947 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libgdsqlite.macos.template_release differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libmacos.libgdsqlite.template_release b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libmacos.libgdsqlite.template_release new file mode 100644 index 00000000..c480fede Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.macos.template_release.framework/libmacos.libgdsqlite.template_release differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.nothreads.wasm b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.nothreads.wasm new file mode 100644 index 00000000..194b9565 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.nothreads.wasm differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.wasm b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.wasm new file mode 100644 index 00000000..1b0221b7 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_debug.wasm32.wasm differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.nothreads.wasm b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.nothreads.wasm new file mode 100644 index 00000000..5ab37a67 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.nothreads.wasm differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.wasm b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.wasm new file mode 100644 index 00000000..a7382d53 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.web.template_release.wasm32.wasm differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_debug.x86_64.dll b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_debug.x86_64.dll new file mode 100644 index 00000000..0fd29716 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_debug.x86_64.dll differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_release.x86_64.dll b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_release.x86_64.dll new file mode 100644 index 00000000..02e1acc2 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgdsqlite.windows.template_release.x86_64.dll differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/Info.plist new file mode 100644 index 00000000..7c5615d1 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + libgodot-cpp.ios.template_debug.arm64.a + LibraryIdentifier + ios-arm64 + LibraryPath + libgodot-cpp.ios.template_debug.arm64.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libgodot-cpp.ios.template_debug.universal.simulator.a + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgodot-cpp.ios.template_debug.universal.simulator.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64/libgodot-cpp.ios.template_debug.arm64.a b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64/libgodot-cpp.ios.template_debug.arm64.a new file mode 100644 index 00000000..15c8dcda Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64/libgodot-cpp.ios.template_debug.arm64.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_debug.universal.simulator.a b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_debug.universal.simulator.a new file mode 100644 index 00000000..93cfcf8d Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_debug.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_debug.universal.simulator.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/Info.plist b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/Info.plist new file mode 100644 index 00000000..dd571681 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + libgodot-cpp.ios.template_release.arm64.a + LibraryIdentifier + ios-arm64 + LibraryPath + libgodot-cpp.ios.template_release.arm64.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libgodot-cpp.ios.template_release.universal.simulator.a + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libgodot-cpp.ios.template_release.universal.simulator.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64/libgodot-cpp.ios.template_release.arm64.a b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64/libgodot-cpp.ios.template_release.arm64.a new file mode 100644 index 00000000..8d380b0f Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64/libgodot-cpp.ios.template_release.arm64.a differ diff --git a/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_release.universal.simulator.a b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_release.universal.simulator.a new file mode 100644 index 00000000..06d1f0b1 Binary files /dev/null and b/addons/gdata_orm/godot-sqlite/bin/libgodot-cpp.ios.template_release.xcframework/ios-arm64_x86_64-simulator/libgodot-cpp.ios.template_release.universal.simulator.a differ diff --git a/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension b/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension new file mode 100644 index 00000000..3c48afcf --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension @@ -0,0 +1,32 @@ +[configuration] + +entry_symbol = "sqlite_library_init" +compatibility_minimum = "4.4" + +[libraries] + +macos.debug = "./bin/libgdsqlite.macos.template_debug.framework" +macos.release = "./bin/libgdsqlite.macos.template_release.framework" +windows.debug.x86_64 = "./bin/libgdsqlite.windows.template_debug.x86_64.dll" +windows.release.x86_64 = "./bin/libgdsqlite.windows.template_release.x86_64.dll" +linux.debug.x86_64 = "./bin/libgdsqlite.linux.template_debug.x86_64.so" +linux.release.x86_64 = "./bin/libgdsqlite.linux.template_release.x86_64.so" +android.debug.arm64 = "./bin/libgdsqlite.android.template_debug.arm64.so" +android.release.arm64 = "./bin/libgdsqlite.android.template_release.arm64.so" +android.debug.x86_64 = "./bin/libgdsqlite.android.template_debug.x86_64.so" +android.release.x86_64 = "./bin/libgdsqlite.android.template_release.x86_64.so" +ios.debug = "./bin/libgdsqlite.ios.template_debug.xcframework" +ios.release = "./bin/libgdsqlite.ios.template_release.xcframework" +web.debug.threads.wasm32 = "./bin/libgdsqlite.web.template_debug.wasm32.wasm" +web.release.threads.wasm32 = "./bin/libgdsqlite.web.template_release.wasm32.wasm" +web.debug.wasm32 = "./bin/libgdsqlite.web.template_debug.wasm32.nothreads.wasm" +web.release.wasm32 = "./bin/libgdsqlite.web.template_release.wasm32.nothreads.wasm" + +[dependencies] + +ios.debug = { + "./bin/libgodot-cpp.ios.template_debug.xcframework": "" +} +ios.release = { + "./bin/libgodot-cpp.ios.template_release.xcframework": "" +} diff --git a/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension.uid b/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension.uid new file mode 100644 index 00000000..2af72abb --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/gdsqlite.gdextension.uid @@ -0,0 +1 @@ +uid://ca6ikrilfs4se diff --git a/addons/gdata_orm/godot-sqlite/godot-sqlite.gd b/addons/gdata_orm/godot-sqlite/godot-sqlite.gd new file mode 100644 index 00000000..95821138 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/godot-sqlite.gd @@ -0,0 +1,14 @@ +# ############################################################################ # +# Copyright © 2019-2025 Piet Bronders & Jeroen De Geeter +# Licensed under the MIT License. +# See LICENSE in the project root for license information. +# ############################################################################ # + +@tool +extends EditorPlugin + +func _enter_tree(): + pass + +func _exit_tree(): + pass diff --git a/addons/gdata_orm/godot-sqlite/godot-sqlite.gd.uid b/addons/gdata_orm/godot-sqlite/godot-sqlite.gd.uid new file mode 100644 index 00000000..029b6578 --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/godot-sqlite.gd.uid @@ -0,0 +1 @@ +uid://cwsfv71x0jo4e diff --git a/addons/gdata_orm/godot-sqlite/plugin.cfg b/addons/gdata_orm/godot-sqlite/plugin.cfg new file mode 100644 index 00000000..7a86a76d --- /dev/null +++ b/addons/gdata_orm/godot-sqlite/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Godot SQLite" +description="GDNative wrapper for SQLite (Godot 4.X+), making it possible to use SQLite databases as data storage in all your future games." +author="Piet Bronders & Jeroen De Geeter" +version="4.5" +script="godot-sqlite.gd" diff --git a/addons/gdata_orm/plugin.cfg b/addons/gdata_orm/plugin.cfg new file mode 100644 index 00000000..71d6ef3b --- /dev/null +++ b/addons/gdata_orm/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="GDataORM" +description="Godot based Data ORM (Object-Relational-Mapping) system based on SQLite." +author="Mario Steele" +version="0.1" +script="plugin.gd" diff --git a/addons/gdata_orm/plugin.gd b/addons/gdata_orm/plugin.gd new file mode 100644 index 00000000..ced9555f --- /dev/null +++ b/addons/gdata_orm/plugin.gd @@ -0,0 +1,23 @@ +@tool +extends EditorPlugin + + +func _enable_plugin() -> void: + EditorInterface.set_plugin_enabled("./godot-sqlite", true) + pass + + +func _disable_plugin() -> void: + EditorInterface.set_plugin_enabled("./godot-sqlite", false) + # Remove autoloads here. + pass + + +func _enter_tree() -> void: + # Initialization of the plugin goes here. + pass + + +func _exit_tree() -> void: + # Clean-up of the plugin goes here. + pass diff --git a/addons/gdata_orm/plugin.gd.uid b/addons/gdata_orm/plugin.gd.uid new file mode 100644 index 00000000..27dc6531 --- /dev/null +++ b/addons/gdata_orm/plugin.gd.uid @@ -0,0 +1 @@ +uid://6kjkv8ueyjhd diff --git a/addons/gdata_orm/sqlite_object.gd b/addons/gdata_orm/sqlite_object.gd new file mode 100644 index 00000000..b3ab920c --- /dev/null +++ b/addons/gdata_orm/sqlite_object.gd @@ -0,0 +1,430 @@ +extends Resource +class_name SQLiteObject +## A Data Object representative of data to store in [SQLite] database. +## +## [SQLiteObject] is the core class for GDataORM. It handles the grunt work +## of defining what table structure is and any special flags that are needed +## for SQLite.[br][br] +## +## [b]Example:[/b] +## [codeblock] +## extends SQLiteObject +## class_name Account +## +## var id: int +## var username: String +## var password: String +## var address: Address +## +## static func _setup() -> void: +## set_table_name(Account, "accounts") +## set_column_flags(Account, "id", Flags.PRIMARY_KEY | Flags.AUTO_INCREMENT | Flags.NOT_NULL) +## set_column_flags(Account, "username", Flags.NOT_NULL) +## set_column_flags(Account, "password", Flags.NOT_NULL) +## [/codeblock] + +## The supported types of [SQLiteObject] +enum DataType { + ## A [bool] Value + BOOL, + ## An [int] Value + INT, + ## A [float] Value + REAL, + ## A Variable Length [String] Value + STRING, + ## A [Dictionary] Value + DICTIONARY, + ## An [Array] Value + ARRAY, + ## A value of a built-in Godot DataType, or Object of a Custom Class. + GODOT_DATATYPE, + ## A Fixed-size [String] value, like [PackedStringArray] + CHAR, + ## A Binary value, like [PackedByteArray] + BLOB +} + +const _BaseTypes = { + TYPE_BOOL: DataType.BOOL, + TYPE_INT: DataType.INT, + TYPE_FLOAT: DataType.REAL, + TYPE_STRING: DataType.STRING, + TYPE_DICTIONARY: DataType.DICTIONARY, + TYPE_ARRAY: DataType.ARRAY, +} + +const _DEFINITION = [ + "int", + "int", + "real", + "text", + "text", + "text", + "blob", + "char(%d)", + "blob" +] + +## SQLite flags used for column definitions. +enum Flags { + ## No Flags Associated with this Column + NONE = 1 << 0, + ## Column must not be Null. + NOT_NULL = 1 << 1, + ## Column must be Unique + UNIQUE = 1 << 2, + ## Column has a Default value. + DEFAULT = 1 << 3, + ## Column is defined as a Primary Key for this table. + PRIMARY_KEY = 1 << 4, + ## Column is defined as auto-incrementing. + AUTO_INCREMENT = 1 << 5, + ## Column is a Foreign Key (See [SQLite] about Foreign Keys) + FOREIGN_KEY = 1 << 6, +} + +class TableDefs: + var columns: Dictionary[String, Dictionary] = {} + var types: Dictionary[String, DataType] = {} + var klass: GDScript + var table_name: String + +static var _tables: Dictionary[GDScript, TableDefs] = {} +static var _registry: Dictionary[String, GDScript] = {} +var _db: SQLite + +## A debugging utility to see what classes have been registered with [SQLiteObject]. +## This is printed out to the terminal/output window for easy review. +static func print_registered_classes() -> void: + print("SQLiteObject Registered Classes:") + for klass_name in _registry: + print(klass_name) + +## A debugging utility to see the structure of all the classes registered with [SQLiteObject]. +## This is printed out to the terminal/output window for easy review. +static func print_data_structure() -> void: + print("SQLite Object Data Structure:") + print("-----------------------------") + for klass in _tables: + var table = _tables[klass] + print("SQLiteObject>%s" % klass.get_global_name()) + print("Table Name: %s" % table.table_name) + print("COLUMNS:") + + for column in table.columns: + var keys: Array = table.columns[column].keys().filter(func(x): return x != "data_type") + var columns := [table.columns[column].data_type] + columns.append_array(keys) + print("\t%s(DataType.%s) - SQLite: (%s)" % [ + column, + DataType.find_key(table.types[column]), + ", ".join(columns) + ]) + print("") + pass + +## This function is called once when setting up the class. This is automatically done with classes +## that are registered as a [DbSet] by the [method Context.setup] static function call. +static func setup(klass: GDScript) -> void: + _registry[klass.get_global_name()] = klass + var table: TableDefs + if _tables.has(klass): + table = _tables[klass] + else: + table = TableDefs.new() + table.klass = klass + table.table_name = klass.get_global_name() + _tables[klass] = table + + for prop in klass.get_script_property_list(): + if not prop.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + continue + + if prop.name.begins_with("_"): + continue + + var def = {} + if _BaseTypes.has(prop.type): + def.data_type = _DEFINITION[_BaseTypes[prop.type]] + table.types[prop.name] = _BaseTypes[prop.type] + else: + def.data_type = _DEFINITION[DataType.GODOT_DATATYPE] + table.types[prop.name] = DataType.GODOT_DATATYPE + + table.columns[prop.name] = def + + klass._setup() + +## This is a virtual function that is called when setup() is called. This allows you to +## setup the data class information such as Column Flags, Table Name and Column Types. +static func _setup() -> void: + push_warning("No setup has been defined for this class. No special column flags or types will be used.") + +## This function allows you to set SQLite specific flags for columns, when storing the data. +## This function should only be called in [method SQLiteObject._setup] which is part of the +## initialization of the data.[br][br] +## [b]Example:[/b] +## [codeblock] +## static func _setup() -> void: +## # Ensure ID is an Auto-Increment Primary key in the database, that is not allowed to be null. +## set_column_flag(MyDataClass, "id", Flags.PRIMARY_KEY | Flags.AUTO_INCREMENT | Flags.NOT_NULL) +## # Ensure that name is not null in the database, and that it doesn't match any other row of data. +## set_column_flag(MyDataClass, "name", Flags.NOT_NULL | Flags.UNIQUE) +## [/codeblock] +static func set_column_flags(klass: GDScript, column: String, flags: int, extra_params: Dictionary = {}) -> void: + assert(_tables.has(klass), "Setup must be called first, before setting any column flags!") + assert(_tables[klass].columns.has(column), "Column has not been defined! Make sure to declare the variable first!") + + var data_type = _tables[klass].types[column] + var col_def = _tables[klass].columns[column] + + if flags & Flags.DEFAULT and not extra_params.has("default"): + assert(false,"Attempting to set a default, without defining it in extra parameters!") + if flags & Flags.AUTO_INCREMENT and not [DataType.INT, DataType.REAL].has(data_type): + assert(false, "Attempting to set Auto Increment flag on Non-Integer column!") + if flags & Flags.FOREIGN_KEY: + if not extra_params.has("table"): + assert(false, "Attempting to set Foreign Key flag without defining the Table it associates with!") + if not extra_params.has("foreign_key"): + assert(false, "Attempting to set Foreign Key flag without defining the Foreign Key!") + + + if flags & Flags.NOT_NULL: col_def.not_null = true + if flags & Flags.UNIQUE: col_def.unique = true + if flags & Flags.DEFAULT: col_def.default = extra_params.default + if flags & Flags.AUTO_INCREMENT: col_def.auto_increment = true + if flags & Flags.PRIMARY_KEY: col_def.primary_key = true + if flags & Flags.FOREIGN_KEY: + col_def.foreign_key = extra_params.foreign_key + col_def.foreign_table = extra_params.table + _tables[klass].columns[column] = col_def + +## Sets the table name to use in the [SQLite] database for storing/fetching data +## from the database. +static func set_table_name(klass: GDScript, table_name: String) -> void: + assert(_tables.has(klass), "Setup must be called first, before setting the table name!") + _tables[klass].table_name = table_name if table_name != "" else klass.get_global_name() + +## Sets the column type of [enum SQLiteObject.DataType] along with any extra parameters needed.[br][br] +## [b][color=red]NOTE:[/color][/b] Only use this function if you know what you are doing. GDataORM +## attempts to match the SQLite data type, with the Godot data type as best as possible. +static func set_column_type(klass: GDScript, column: String, type: DataType, extra_params: Dictionary = {}) -> void: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + assert(_tables[klass].columns.has(column), "Column has not been defined! Make sure to declare the variable first!") + + if type == DataType.CHAR and not extra_params.has("size"): + assert(false, "Attempting to set Column type to CHAR without a size parameter!") + + _tables[klass].types[column] = _DEFINITION[type] if type != DataType.CHAR else _DEFINITION[type] % extra_params.size + +## Sets a variable that has been defined in the class, to be ignored, so as to not persist the data in the +## [SQLite] database. The variable must be defined, in order for it to be ignored.[br][br] +## [b][color=red]NOTE:[/color][/b] By default, GDataORM ignore's any variables that start with [code]_[/code] character. +static func ignore_column(klass: GDScript, column: String) -> void: + assert(_tables.has(klass), "Setup must be called first, before ignoring any column types!") + assert(_tables[klass].columns.has(column), "Column has not been defined! Make sure to declare the variable first!") + + _tables[klass].types.erase(column) + _tables[klass].columns.erase(column) + +## Adds a variable that is normally ignored in the class, to not be ignoerd, so that it can persist the data +## in the [SQLite] database. The variable must be defined, in order for this function to succeed. +static func add_column(klass: GDScript, column: String) -> void: + assert(_tables.has(klass), "Setup must be called first, before adding any column types!") + var props = klass.get_property_list() + var res = props.filter(func(x): return x.name == column) + assert(res.size() > 0, "You cannot add a column, that does not have the variable defined for it!") + + var prop = res[0] + var def = {} + if _BaseTypes.has(prop.type): + def.data_type = _DEFINITION[_BaseTypes[prop.type]] + _tables[klass].types[prop.name] = _BaseTypes[prop.type] + else: + def.data_type = _DEFINITION[DataType.GODOT_DATATYPE] + _tables[klass].types[prop.name] = DataType.GODOT_DATATYPE + + _tables[klass].columns[prop.name] = def + +static func _create_table(db: SQLite, klass: GDScript, drop_if_exists = false) -> void: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + assert(not _tables[klass].columns.is_empty(), "No columns has been defined, either no variables are defined in the GDScript source, or setup was not called first!") + if _table_exists(db, klass): + if drop_if_exists: + db.drop_table(_tables[klass].table_name) + else: + assert(false, "Table already exists!") + db.create_table(_tables[klass].table_name, _tables[klass].columns) + +static func _table_exists(db: SQLite, klass: GDScript) -> bool: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + var table := _tables[klass] + db.query_with_bindings("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", [table.table_name]) + return not db.query_result.is_empty() + +static func _has_id(db: SQLite, klass: GDScript, id: Variant) -> bool: + var primary_key = _get_primary_key(klass) + var table := _tables[klass] + if typeof(id) == TYPE_STRING: + db.query_with_bindings("SELECT ? FROM ? WHERE ?='?'", [primary_key, table.table_name, primary_key, id]) + else: + db.query_with_bindings("SELECT ? FROM ? WHERE ?=?;", [primary_key, table.table_name, primary_key, id]) + return not db.query_result.is_empty() + +static func _get_primary_key(klass: GDScript) -> String: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + var table := _tables[klass] + var primary_key: String = "" + for column in table.columns: + if table.columns[column].has("primary_key"): + primary_key = column + break + + assert(primary_key != "", "No primary key has been defined!") + return primary_key + +static func _populate_object(table: TableDefs, obj: SQLiteObject, data: Dictionary) -> void: + var props = obj.get_property_list() + for key in data: + if not props.any(func(x): return x.name == key): + continue + var prop = props.filter(func(x): return x.name == key)[0] + if (table.types[key] == DataType.ARRAY or + table.types[key] == DataType.DICTIONARY): + obj.get(key).assign(JSON.parse_string(data[key])) + elif table.types[key] == DataType.GODOT_DATATYPE: + if _registry.has(prop.class_name): + var klass := _registry[prop.class_name] + var cond := Condition.new() + var pk: String = _get_primary_key(klass) + cond.equal(pk, bytes_to_var(data[key])) + var nobj = _find_one(obj._db, klass, cond) + obj.set(key, nobj) + else: + obj.set(key, bytes_to_var(data[key])) + else: + obj.set(key, data[key]) + +static func _find_one(db: SQLite, klass: GDScript, conditions: Condition) -> SQLiteObject: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + var table := _tables[klass] + var res := db.select_rows(table.table_name, conditions.to_string(), table.columns.keys()) + + if res.is_empty(): + return null + else: + var obj = klass.new() + obj._db = db + _populate_object(table, obj, res[0]) + return obj + +static func _find_many(db: SQLite, klass: GDScript, conditions: Condition) -> Array: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + var table := _tables[klass] + var objs: Array = [] + var res = db.select_rows(table.table_name, conditions.to_string(), table.columns.keys()) + + for data in res: + var obj = klass.new() + obj._db = db + _populate_object(table, obj, data) + objs.append(obj) + return objs + +static func _all(db: SQLite, klass: GDScript) -> Array: + assert(_tables.has(klass), "Setup must be called first, before setting any column types!") + var table := _tables[klass] + var objs: Array = [] + var res = db.select_rows(table.table_name, "", table.columns.keys()) + + for data in res: + var obj = klass.new() + obj._db = db + _populate_object(table, obj, data) + objs.append(obj) + return objs + +## Verify that the [SQLiteObject] exists in the database. +func exists() -> bool: + assert(_tables.has(self.get_script()), "Setup must be called first, before setting any column types!") + assert(_db, "exists(): This instance was not fetched from the database, or has not been added to a DbSet!") + var table := _tables[self.get_script()] + var primary_key = _get_primary_key(self.get_script()) + assert(primary_key != "", "A Primary Key has not been defined for this class.") + var res = _db.select_rows(table.table_name, + Condition.new().equal(primary_key, self.get(primary_key)).to_string(), + [primary_key]) + return not res.is_empty() + +## Saves the [SQLiteObject] to the database file.[br][br] +## [b][color=red]NOTE:[/color][/b] Of special note, an object needs to be added to a [DbSet] first through +## [method DbSet.append] for this function to work. [method DbSet.append] will save the object when +## it is first added. This function is mostly for recording updates to the [SQLiteObject] data. +func save() -> void: + assert(_tables.has(self.get_script()), "Setup must be called first, before setting any column types!") + assert(_db, "save(): This instance was not fetched from the database, or has not been added to a DbSet!") + var table := _tables[self.get_script()] + var primary_key = _get_primary_key(self.get_script()) + + var sql_data = {} + var data: Variant + for key in table.columns.keys(): + data = get(key) + if (table.types[key] == DataType.ARRAY or + table.types[key] == DataType.DICTIONARY + ): + sql_data[key] = JSON.stringify(data) + elif table.types[key] == DataType.GODOT_DATATYPE: + if typeof(data) == TYPE_OBJECT: + if _registry.has(data.get_script().get_global_name()): + var pk := _get_primary_key(data.get_script()) + var pk_val = data.get(pk) + sql_data[key] = var_to_bytes(pk_val) + else: + sql_data[key] = var_to_bytes(data) + else: + sql_data[key] = var_to_bytes(data) + else: + sql_data[key] = data + + if primary_key != "" and exists(): + _db.update_rows(table.table_name,Condition.new().equal(primary_key, get(primary_key)).to_string(), sql_data) + else: + if primary_key != "" and table.columns[primary_key].auto_increment: + sql_data.erase(primary_key) + + _db.insert_row(table.table_name, sql_data) + + if primary_key != "" and table.columns[primary_key].auto_increment: + var cond := Condition.new().equal("name","%s" % table.table_name) + var res := _db.select_rows("sqlite_sequence", cond.to_string(), ["seq"]) + assert(not res.is_empty(), "Failed to insert record into %s." % [table.table_name]) + set(primary_key, res[0].seq) + +## Removes the [SQLiteObject] from the database. This will fail, if the object was not fetched +## from the database first. You can also use [method DbSet.erase] to remove an object from the +## database. +func delete() -> void: + assert(_tables.has(self.get_script()), "Setup must be called first, before setting any column types!") + assert(_db, "delete(): This instance was not fetched from the database, or has not been added to a DbSet!") + var table := _tables[self.get_script()] + var primary_key = _get_primary_key(self.get_script()) + + assert(primary_key != "", "In order to delete data from the database, it must have a primary key!") + + if not exists(): + push_warning("Attempting to delete a record that doesn't exist!") + return + + _db.delete_rows(table.table_name, Condition.new().equal(primary_key, get(primary_key)).to_string()) + +func _to_string() -> String: + assert(_tables.has(self.get_script()), "Setup must be called first, before setting any column types!") + var table := _tables[self.get_script()] + var primary_key = _get_primary_key(self.get_script()) + var kname = self.get_script().get_global_name() + if primary_key != "": + return "<%s:%s:%s>" % [kname, table.table_name, get(primary_key)] + else: + return "<%s:%s:G-%s>" % [kname, table.table_name, get_instance_id()] diff --git a/addons/gdata_orm/sqlite_object.gd.uid b/addons/gdata_orm/sqlite_object.gd.uid new file mode 100644 index 00000000..241e202c --- /dev/null +++ b/addons/gdata_orm/sqlite_object.gd.uid @@ -0,0 +1 @@ +uid://uvaml64lmuws diff --git a/addons/gde_gozen/LICENSE b/addons/gde_gozen/LICENSE new file mode 100644 index 00000000..8000a6fa --- /dev/null +++ b/addons/gde_gozen/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/addons/gde_gozen/README.md b/addons/gde_gozen/README.md new file mode 100644 index 00000000..7cb66875 --- /dev/null +++ b/addons/gde_gozen/README.md @@ -0,0 +1,17 @@ +# GDE GoZen +## Adding the addon to your project +Put this folder, `gde_gozen`, inside of a folder called `addons` inside of your Godot project and re-open your project. After reloading you will have access to a new node, `VideoPlayback`. This node has a lot of documentation comments so by pressing F1 inside Godot and search for the node `VideoPlayback`, you'll find it's documentation and more notes on how to use it. + +## Videos in the file tree +To see video files in your projects file tree, you need to add `mp4` and any other video extensions you might use to your Editor settings in `docks/filesystem/other_file_extensions`. + +## Exporting your project +1. When exporting, you'll have your executable and the GDE GoZen library file, these do need to be both shared for the application to work. +2. Also, you will have to add `*.mp4` and other extension names of your video files to the resources which need to get exported for each platform you want to export for, otherwise your video files will not be included in your final export. + +## Help needed? +You can go to the [GitHub repo](https://github.com/VoylinsGamedevJourney/gde_gozen/issues) to report problems, or visit out [Discord server](discord.gg/BdbUf7VKYC) for help/advice. + +> This software uses libraries from the FFmpeg project under the LGPLv2.1 + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R4M1UM6) diff --git a/addons/gde_gozen/bin/libgozen.android.template_debug.arm32.so b/addons/gde_gozen/bin/libgozen.android.template_debug.arm32.so new file mode 100644 index 00000000..29b73292 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.android.template_debug.arm32.so differ diff --git a/addons/gde_gozen/bin/libgozen.android.template_debug.arm64.so b/addons/gde_gozen/bin/libgozen.android.template_debug.arm64.so new file mode 100644 index 00000000..eb0da7fb Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.android.template_debug.arm64.so differ diff --git a/addons/gde_gozen/bin/libgozen.android.template_release.arm32.so b/addons/gde_gozen/bin/libgozen.android.template_release.arm32.so new file mode 100644 index 00000000..2828a082 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.android.template_release.arm32.so differ diff --git a/addons/gde_gozen/bin/libgozen.android.template_release.arm64.so b/addons/gde_gozen/bin/libgozen.android.template_release.arm64.so new file mode 100644 index 00000000..5282fe15 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.android.template_release.arm64.so differ diff --git a/addons/gde_gozen/bin/libgozen.linux.template_debug.arm64.so b/addons/gde_gozen/bin/libgozen.linux.template_debug.arm64.so new file mode 100644 index 00000000..428399bc Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.linux.template_debug.arm64.so differ diff --git a/addons/gde_gozen/bin/libgozen.linux.template_debug.x86_64.so b/addons/gde_gozen/bin/libgozen.linux.template_debug.x86_64.so new file mode 100644 index 00000000..ff4477ad Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.linux.template_debug.x86_64.so differ diff --git a/addons/gde_gozen/bin/libgozen.linux.template_release.arm64.so b/addons/gde_gozen/bin/libgozen.linux.template_release.arm64.so new file mode 100644 index 00000000..c0dc2273 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.linux.template_release.arm64.so differ diff --git a/addons/gde_gozen/bin/libgozen.linux.template_release.x86_64.so b/addons/gde_gozen/bin/libgozen.linux.template_release.x86_64.so new file mode 100644 index 00000000..d7c47f81 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.linux.template_release.x86_64.so differ diff --git a/addons/gde_gozen/bin/libgozen.macos.template_debug.arm64.dylib b/addons/gde_gozen/bin/libgozen.macos.template_debug.arm64.dylib new file mode 100644 index 00000000..8fa036bd Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.macos.template_debug.arm64.dylib differ diff --git a/addons/gde_gozen/bin/libgozen.macos.template_debug.x86_64.dylib b/addons/gde_gozen/bin/libgozen.macos.template_debug.x86_64.dylib new file mode 100644 index 00000000..14792cad Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.macos.template_debug.x86_64.dylib differ diff --git a/addons/gde_gozen/bin/libgozen.macos.template_release.arm64.dylib b/addons/gde_gozen/bin/libgozen.macos.template_release.arm64.dylib new file mode 100644 index 00000000..f1b0be50 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.macos.template_release.arm64.dylib differ diff --git a/addons/gde_gozen/bin/libgozen.macos.template_release.x86_64.dylib b/addons/gde_gozen/bin/libgozen.macos.template_release.x86_64.dylib new file mode 100644 index 00000000..75f3c746 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.macos.template_release.x86_64.dylib differ diff --git a/addons/gde_gozen/bin/libgozen.windows.template_debug.x86_64.dll b/addons/gde_gozen/bin/libgozen.windows.template_debug.x86_64.dll new file mode 100644 index 00000000..f8a291a4 Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.windows.template_debug.x86_64.dll differ diff --git a/addons/gde_gozen/bin/libgozen.windows.template_release.x86_64.dll b/addons/gde_gozen/bin/libgozen.windows.template_release.x86_64.dll new file mode 100644 index 00000000..afeb74da Binary files /dev/null and b/addons/gde_gozen/bin/libgozen.windows.template_release.x86_64.dll differ diff --git a/addons/gde_gozen/gozen.gdextension b/addons/gde_gozen/gozen.gdextension new file mode 100644 index 00000000..c3da67b4 --- /dev/null +++ b/addons/gde_gozen/gozen.gdextension @@ -0,0 +1,29 @@ +[configuration] + +entry_symbol = "gozen_library_init" +compatibility_minimum = "4.3" + +[libraries] + +linux.debug.x86_64 = "res://addons/gde_gozen/bin/libgozen.linux.template_debug.x86_64.so" +linux.release.x86_64 = "res://addons/gde_gozen/bin/libgozen.linux.template_release.x86_64.so" +linux.debug.arm64 = "res://addons/gde_gozen/bin/libgozen.linux.template_debug.arm64.so" +linux.release.arm64 = "res://addons/gde_gozen/bin/libgozen.linux.template_release.arm64.so" + +windows.debug.x86_64 = "res://addons/gde_gozen/bin/libgozen.windows.template_debug.x86_64.dll" +windows.release.x86_64 = "res://addons/gde_gozen/bin/libgozen.windows.template_release.x86_64.dll" +windows.debug.arm64 = "res://addons/gde_gozen/bin/libgozen.windows.template_debug.arm64.dll" +windows.release.arm64 = "res://addons/gde_gozen/bin/libgozen.windows.template_release.arm64.dll" + +macos.debug.x86_64 = "res://addons/gde_gozen/bin/libgozen.macos.template_debug.x86_64.dylib" +macos.release.x86_64 = "res://addons/gde_gozen/bin/libgozen.macos.template_release.x86_64.dylib" +macos.debug.arm64 = "res://addons/gde_gozen/bin/libgozen.macos.template_debug.arm64.dylib" +macos.release.arm64 = "res://addons/gde_gozen/bin/libgozen.macos.template_release.arm64.dylib" + +web.debug.wasm32 = "res://addons/gde_gozen/bin/libgozen.web.template_debug.wasm32.wasm" +web.release.wasm32 = "res://addons/gde_gozen/bin/libgozen.web.template_release.wasm32.wasm" + +android.debug.arm64 = "res://addons/gde_gozen/bin/libgozen.android.template_debug.arm64.so" +android.release.arm64 = "res://addons/gde_gozen/bin/libgozen.android.template_release.arm64.so" +android.debug.arm32 = "res://addons/gde_gozen/bin/libgozen.android.template_debug.arm32.so" +android.release.arm32 = "res://addons/gde_gozen/bin/libgozen.android.template_release.arm32.so" diff --git a/addons/gde_gozen/gozen.gdextension.uid b/addons/gde_gozen/gozen.gdextension.uid new file mode 100644 index 00000000..b1a6895e --- /dev/null +++ b/addons/gde_gozen/gozen.gdextension.uid @@ -0,0 +1 @@ +uid://bbi0kdfadrphl diff --git a/addons/gde_gozen/icon.svg b/addons/gde_gozen/icon.svg new file mode 100644 index 00000000..fc780213 --- /dev/null +++ b/addons/gde_gozen/icon.svg @@ -0,0 +1,252 @@ + + + + diff --git a/addons/gde_gozen/icon.svg.import b/addons/gde_gozen/icon.svg.import new file mode 100644 index 00000000..6b4a127a --- /dev/null +++ b/addons/gde_gozen/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bijjjb1qmqf7f" +path="res://.godot/imported/icon.svg-ac53c9c419a88eda808214a4d44243c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/gde_gozen/icon.svg" +dest_files=["res://.godot/imported/icon.svg-ac53c9c419a88eda808214a4d44243c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/gde_gozen/icon.webp b/addons/gde_gozen/icon.webp new file mode 100644 index 00000000..6b21c2a8 Binary files /dev/null and b/addons/gde_gozen/icon.webp differ diff --git a/addons/gde_gozen/icon.webp.import b/addons/gde_gozen/icon.webp.import new file mode 100644 index 00000000..075b07c1 --- /dev/null +++ b/addons/gde_gozen/icon.webp.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbk4jierdjfrf" +path="res://.godot/imported/icon.webp-a5a23a9a73b8ba8140cf260e6bfffac7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/gde_gozen/icon.webp" +dest_files=["res://.godot/imported/icon.webp-a5a23a9a73b8ba8140cf260e6bfffac7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/gde_gozen/plugin.cfg b/addons/gde_gozen/plugin.cfg new file mode 100644 index 00000000..59c444d1 --- /dev/null +++ b/addons/gde_gozen/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="gde_gozen" +description="Providing performant video playback." +author="Voylin's Gamedev Journey" +version="v9.1" +script="plugin.gd" diff --git a/addons/gde_gozen/plugin.gd b/addons/gde_gozen/plugin.gd new file mode 100644 index 00000000..fa2aefb0 --- /dev/null +++ b/addons/gde_gozen/plugin.gd @@ -0,0 +1,17 @@ +@tool +class_name GoZenServer +extends EditorPlugin +## GoZenServer is only used for adding the node to the node list. + + + +func _enter_tree() -> void: + add_custom_type( + "VideoPlayback", "Control", + load("res://addons/gde_gozen/video_playback.gd"), + load("res://addons/gde_gozen/icon.webp")) + + +func _exit_tree() -> void: + remove_custom_type("VideoPlayback") + diff --git a/addons/gde_gozen/plugin.gd.uid b/addons/gde_gozen/plugin.gd.uid new file mode 100644 index 00000000..874d7de7 --- /dev/null +++ b/addons/gde_gozen/plugin.gd.uid @@ -0,0 +1 @@ +uid://1vr5dotnqnh5 diff --git a/addons/gde_gozen/video_playback.gd b/addons/gde_gozen/video_playback.gd new file mode 100644 index 00000000..5bb93944 --- /dev/null +++ b/addons/gde_gozen/video_playback.gd @@ -0,0 +1,610 @@ +class_name VideoPlayback +extends Control +## Video playback and seeking inside of Godot. +## +## To use this node, just add it anywhere and resize it to the desired size. Use the function [code]set_video_path(new_path)[/code] and the video will load. Take in mind that long video's can take a second or longer to load. If this is an issue you can preload the Video on startup of your project and set the video variable yourself, just remember to use the function [code]update_video()[/code] before the moment that you'd like to use it. + +enum COLOR_PROFILE { AUTO, BT470, BT601, BT709, BT2020, BT2100 } +enum STREAM_TYPE { VIDEO = 0, AUDIO = 1, SUBTITLE = 2 } + + +signal frame_changed(frame_nr: int) ## Emitted when the current frame has changed, for showing and skipped frames. +signal next_frame_called(frame_nr: int) ## Emitted when a new frame is showing. + +signal video_loaded ## Emitted when the video is ready for playback. +signal video_ended ## Emitted when the last frame has been shown. + +signal playback_started ## Emitted when playback started/resumed. +signal playback_paused ## Emitted when playback is paused. +signal playback_ready ## Emitted when the node if fully setup and ready for playback. + + +const SHADER_PATH: String = "res://addons/gde_gozen/yuv_to_rgb.gdshader" +const PLAYBACK_SPEED_MIN: float = 0.25 +const PLAYBACK_SPEED_MAX: float = 4 +const AUDIO_OFFSET_THRESHOLD: float = 0.1 + + +@export_file var path: String = "": set = set_video_path ## Full path to video file. +@export var enable_audio: bool = true ## Enable/Disable audio playback. When setting this on false before loading the audio, the audio playback won't be loaded meaning that the video will load faster. If you want audio but only disable it at certain moments, switch this value to false *after* the video is loaded. +@export var audio_speed_to_sync: bool = false ## Enable/Disable a slight audio playback speed increase/reduction when syncing audio and video to avoid a hard cut. +@export var enable_auto_play: bool = false ## Enable/disable auto video playback. +@export_range(PLAYBACK_SPEED_MIN, PLAYBACK_SPEED_MAX, 0.05) +var playback_speed: float = 1.0: set = set_playback_speed ## Adjust the video playback speed, 0.5 = half the speed and 2 = double the speed. +@export var pitch_adjust: bool = true: set = set_pitch_adjust ## When changing playback speed, do you want the pitch to change or stay the same? +@export var loop: bool = false ## Enable/disable looping on video_ended. +@export_group("Extra's") +@export var color_profile: COLOR_PROFILE = COLOR_PROFILE.AUTO: set = _set_color_profile ## Force a specific color profile if needed. +@export var debug: bool = false ## Enable/disable the printing of debug info. + +var video: GoZenVideo = null ## Video class object of GDE GoZen which interacts with video files through FFmpeg. + +var video_texture: TextureRect = TextureRect.new() ## The texture rect is the view of the video, you can adjust the scaling options as you like, it is set to always center and scale the image to fit within the main VideoPlayback node size. +var audio_player: AudioStreamPlayer = AudioStreamPlayer.new() ## Audio player is the AudioStreamPlayer which handles the audio playback for the video, only mess with the settings if you know what you are doing and know what you'd like to achieve. + +var is_playing: bool = false ## Bool to check if the video is currently playing or not. +var current_frame: int = 0: set = _set_current_frame ## Current frame number which the video playback is at. + +var video_streams: PackedInt32Array = [] ## List of video streams in the video file. +var audio_streams: PackedInt32Array = [] ## List of audio streams in the video file. +var subtitle_streams: PackedInt32Array = [] ## List of subtitle streams in the video file. +var chapters: Array[Chapter] = [] ## List of chapters in the video file. + +var _time_elapsed: float = 0. +var _frame_time: float = 0 +var _skips: int = 0 + +var _rotation: int = 0 +var _padding: int = 0 +var _frame_rate: float = 0. +var _frame_count: int = 0 +var _has_alpha: bool = false + +var _resolution: Vector2i = Vector2i.ZERO +var _shader_material: ShaderMaterial = null + +var _video_thread: int = -1 +var _audio_pitch_effect: AudioEffectPitchShift = AudioEffectPitchShift.new() + +var y_texture: ImageTexture +var u_texture: ImageTexture +var v_texture: ImageTexture +var a_texture: ImageTexture + + + +#------------------------------------------------ TREE FUNCTIONS +func _enter_tree() -> void: + var empty_image: Image = Image.create_empty(2,2,false, Image.FORMAT_R8) + + y_texture = ImageTexture.create_from_image(empty_image) + u_texture = ImageTexture.create_from_image(empty_image) + v_texture = ImageTexture.create_from_image(empty_image) + a_texture = ImageTexture.create_from_image(empty_image) + + _shader_material = ShaderMaterial.new() + _shader_material.shader = preload(SHADER_PATH) + + video_texture.material = _shader_material + video_texture.texture = ImageTexture.new() + video_texture.anchor_right = TextureRect.ANCHOR_END + video_texture.anchor_bottom = TextureRect.ANCHOR_END + video_texture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + video_texture.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + + add_child(video_texture) + add_child(audio_player) + + AudioServer.add_bus() + audio_player.bus = AudioServer.get_bus_name(AudioServer.bus_count - 1) + AudioServer.add_bus_effect(AudioServer.bus_count - 1, _audio_pitch_effect) + + if debug and OS.get_name().to_lower() != "web": + _print_system_debug() + + +func _exit_tree() -> void: + # Making certain no remaining tasks are running in separate threads. + if _video_thread != -1: + var error: int = WorkerThreadPool.wait_for_task_completion(_video_thread) + + if error != OK: + printerr("Something went wrong waiting for task completion! %s" % error) + _video_thread = -1 + + if video != null: + close() + + AudioServer.remove_bus(AudioServer.get_bus_index(audio_player.bus)) + + +func _ready() -> void: + playback_ready.emit() + + +#------------------------------------------------ VIDEO DATA HANDLING +## This is the starting point for video playback, provide a path of where +## the video file can be found and it will load a Video object. After which +## [code]_update_video()[/code] get's run and set's the first frame image. +func set_video_path(new_path: String) -> void: + if video != null: + close() + + if !is_node_ready(): + await ready + if !get_tree().root.is_node_ready(): + await get_tree().root.ready + + audio_player.stream = null # Cleaning up the stream just in case. + + if new_path == "" or new_path.ends_with(".tscn"): + return + elif new_path.split(":")[0] == "uid": + new_path = ResourceUID.get_id_path(ResourceUID.text_to_id(new_path)) + + path = new_path + video = GoZenVideo.new() + + if debug: + video.enable_debug() + else: + video.disable_debug() + + _video_thread = WorkerThreadPool.add_task(_open_video) + + if enable_audio: + _open_audio() + + +## Update the video manually by providing a GoZenVideo instance and an optional AudioStreamWAV. +func update_video(video_instance: GoZenVideo) -> void: + if video != null: + close() + + _update_video(video_instance) + _open_audio() + + +## Only run this function after manually having added a Video object to the `video` variable. A good reason for doing this is to load your video's at startup time to prevent your program for freezing for a second when loading in big video files. Some video formats load faster then others so if you are experiencing issues with long loading times, try to use this function and create the video object on startup, or try switching the video format which you are using. +func _update_video(new_video: GoZenVideo) -> void: + video = new_video + if !is_open(): + printerr("Video isn't open!") + return + + var image: Image + var rotation_radians: float = deg_to_rad(video.get_rotation()) + + is_playing = false + current_frame = 0 + + # Getting video data + _padding = video.get_padding() + _rotation = video.get_rotation() + _frame_rate = video.get_framerate() + _resolution = video.get_resolution() + _frame_count = video.get_frame_count() + _has_alpha = video.get_has_alpha() + + video_streams = video.get_streams(STREAM_TYPE.VIDEO) + audio_streams = video.get_streams(STREAM_TYPE.AUDIO) + subtitle_streams = video.get_streams(STREAM_TYPE.SUBTITLE) + + chapters.clear() + for i: int in range(video.get_chapter_count()): + @warning_ignore("UNSAFE_CALL_ARGUMENT") + var chapter: Chapter = Chapter.new( + video.get_chapter_start(i), + video.get_chapter_end(i), + video.get_chapter_metadata(i).get("title", "") + ) + chapters.append(chapter) + + if abs(_rotation) == 90: + image = Image.create_empty(_resolution.y, _resolution.x, false, Image.FORMAT_R8) + else: + image = Image.create_empty(_resolution.x, _resolution.y, false, Image.FORMAT_R8) + + image.fill(Color.WHITE) + + if debug: + _print_video_debug() + + @warning_ignore("UNSAFE_METHOD_ACCESS") + video_texture.texture.set_image(image) + + # Applying shader params. + _shader_material.set_shader_parameter("resolution", video.get_actual_resolution()) + _shader_material.set_shader_parameter("full_color", video.is_full_color_range()) + _shader_material.set_shader_parameter("interlaced", video.get_interlaced()) + _shader_material.set_shader_parameter("rotation", rotation_radians) + _set_color_profile() + + y_texture.set_image(video.get_y_data()) + u_texture.set_image(video.get_u_data()) + v_texture.set_image(video.get_v_data()) + a_texture.set_image(video.get_a_data() if _has_alpha else image) + + _shader_material.set_shader_parameter("y_data", y_texture) + _shader_material.set_shader_parameter("u_data", u_texture) + _shader_material.set_shader_parameter("v_data", v_texture) + _shader_material.set_shader_parameter("a_data", a_texture) + + set_playback_speed(playback_speed) + video_loaded.emit() + + +## Sometimes color profiles are unknown from video files and in case that happens, the colors might be slightly off. Changing the export variable `color_profile` might help fixing the colors. +func _set_color_profile(new_profile: COLOR_PROFILE = color_profile) -> void: + var color_data: Vector4 + var profile_str: String = video.get_color_profile() + + color_profile = new_profile + + if new_profile != COLOR_PROFILE.AUTO: + profile_str = str(COLOR_PROFILE.find_key(COLOR_PROFILE.BT2100)).to_lower() + + match profile_str: + "bt2020", "bt2100": color_data = Vector4(1.4746, 0.16455, 0.57135, 1.8814) + "bt601", "bt470": color_data = Vector4(1.402, 0.344136, 0.714136, 1.772) + _: color_data = Vector4(1.5748, 0.1873, 0.4681, 1.8556) # bt709 and unknown + + _shader_material.set_shader_parameter("color_profile", color_data) + + +## Seek frame can be used to switch to a frame number you want. Remember that some video codecs report incorrect video end frames or can't seek to the last couple of frames in a video file which may result in an error. Only use this when going to far distances in the video file, else you can use [code]next_frame()[/code]. +func seek_frame(new_frame_nr: int) -> void: + if !is_open() and new_frame_nr == current_frame: + return + + current_frame = clamp(new_frame_nr, 0, _frame_count) + if video.seek_frame(current_frame): + printerr("Couldn't seek frame!") + else: + _set_frame_image() + + if enable_audio and audio_player.stream.get_length() != 0: + audio_player.set_stream_paused(false) + audio_player.play(current_frame / _frame_rate) + audio_player.set_stream_paused(!is_playing) + + +## Seeking frames can be slow, so when you just need to go a couple of frames ahead, you can use next_frame and set skip to false for the last frame. +func next_frame(skip: bool = false) -> void: + if video.next_frame(skip) and !skip: + _set_frame_image() + next_frame_called.emit(current_frame) + elif !skip: + print("Something went wrong getting next frame!") + + +func close() -> void: + if video != null: + if is_playing: + pause() + + video = null + + +#------------------------------------------------ PLAYBACK HANDLING +func _process(delta: float) -> void: + if is_playing: + _skips = 0 + _time_elapsed += delta + + if _time_elapsed < _frame_time: + return + + while _time_elapsed >= _frame_time and _skips < 5: + _time_elapsed -= _frame_time + current_frame += 1 + _skips += 1 + + if current_frame >= _frame_count: + is_playing = !is_playing + + if enable_audio and audio_player.stream != null: + audio_player.set_stream_paused(true) + + video_ended.emit() + + if loop: + seek_frame(0) + play() + else: + _sync_audio_video() + + while _skips != 1: + next_frame(true) + _skips -= 1 + next_frame() + elif _video_thread != -1: + var error: int = WorkerThreadPool.wait_for_task_completion(_video_thread) + + if error != OK: + printerr("Something went wrong waiting for task completion! %s" % error) + + _video_thread = -1 + _update_video(video) + + if enable_auto_play: + play() + + +## Start the video playback. This will play until reaching the end of the video and then pause and go back to the start. +func play() -> void: + if video != null and !is_open() and is_playing: + return + is_playing = true + + if enable_audio and audio_player.stream.get_length() != 0: + audio_player.set_stream_paused(false) + audio_player.play((current_frame + 1) / _frame_rate) + audio_player.set_stream_paused(!is_playing) + + playback_started.emit() + + +## Pausing the video. +func pause() -> void: + is_playing = false + + if enable_audio and audio_player.stream != null: + audio_player.set_stream_paused(true) + + playback_paused.emit() + + +## Ensures the audio playback is in sync with the video +func _sync_audio_video() -> void: + if _time_elapsed < 1.20: + return + elif enable_audio and audio_player.stream.get_length() != 0: + var audio_offset: float = audio_player.get_playback_position() + AudioServer.get_time_since_last_mix() - (current_frame + 1) / _frame_rate + + if abs(audio_player.get_playback_position() + AudioServer.get_time_since_last_mix() - (current_frame + 1) / _frame_rate) > AUDIO_OFFSET_THRESHOLD: + if debug: print("Audio Sync: time correction: ", audio_offset) + audio_player.seek((current_frame + 1) / _frame_rate) + audio_player.pitch_scale = playback_speed + elif audio_speed_to_sync: + if is_zero_approx(audio_player.pitch_scale - playback_speed): + if audio_offset > AUDIO_OFFSET_THRESHOLD / 2: + audio_player.pitch_scale = playback_speed * 0.99 + if debug: print("Audio Sync: slow down") + elif audio_offset < -AUDIO_OFFSET_THRESHOLD / 2: + audio_player.pitch_scale = playback_speed * 1.01 + if debug: print("Audio Sync: speed up") + else: + if not (audio_player.pitch_scale > playback_speed) != not (audio_offset < 0): + audio_player.pitch_scale = playback_speed + if debug: print("Audio Sync: back to normal") + + +#------------------------------------------------ GETTERS +## Getting the total amount of frames found in the video file. +func get_video_frame_count() -> int: + return _frame_count + + +## Getting the framerate of the video +func get_video_framerate() -> float: + return _frame_rate + + +## Getting the length of the video in seconds +func get_video_length() -> int: + return int(_frame_count / _frame_rate) + + +## Getting the current playback position of the video in seconds +func get_current_playback_position() -> int: + return int(current_frame / _frame_rate) + + +## Getting the rotation in degrees of the video +func get_video_rotation() -> int: + return _rotation + + +## Check the alpha value of a video to know if this video has alpha or not +func is_video_alpha() -> bool: + return _has_alpha + + +## Getting the title of a stream. +func get_stream_title(stream: int) -> String: + if not is_open(): + printerr("Video is not open!") + return "" + + return video.get_stream_metadata(stream).get("title") + + +## Getting the language of a stream. +func get_stream_language(stream: int) -> String: + if not is_open(): + printerr("Video is not open!") + return "" + + return video.get_stream_metadata(stream).get("language") + + +## Checking to see if the video is open or not, trying to run functions without checking if open can crash your project. +func is_open() -> bool: + return video != null and video.is_open() + + +func _get_img_tex(image_data: PackedByteArray, width: int, height: int, r8: bool = true) -> ImageTexture: + var format: Image.Format = Image.FORMAT_R8 if r8 else Image.FORMAT_RG8 + var image: Image = Image.create_from_data(width, height, false, format, image_data) + + return ImageTexture.create_from_image(image) + + +#------------------------------------------------ SETTERS +func _set_current_frame(new_current_frame: int) -> void: + current_frame = new_current_frame + frame_changed.emit(current_frame) + + +func _set_frame_image() -> void: + RenderingServer.texture_2d_update(y_texture.get_rid(), video.get_y_data(), 0) + RenderingServer.texture_2d_update(u_texture.get_rid(), video.get_u_data(), 0) + RenderingServer.texture_2d_update(v_texture.get_rid(), video.get_v_data(), 0) + + if _has_alpha: + RenderingServer.texture_2d_update(a_texture.get_rid(), video.get_a_data(), 0) + + +func set_playback_speed(new_playback_value: float) -> void: + playback_speed = clampf(new_playback_value, 0.5, 2) + _frame_time = (1.0 / _frame_rate) / playback_speed + + if enable_audio and audio_player.stream != null: + audio_player.pitch_scale = playback_speed + _set_pitch_adjust() + + if is_playing: + audio_player.play(current_frame * (1.0 / _frame_rate)) + + +func set_pitch_adjust(new_pitch_value: bool) -> void: + pitch_adjust = new_pitch_value + _set_pitch_adjust() + + +func _set_pitch_adjust() -> void: + if pitch_adjust: + _audio_pitch_effect.pitch_scale = clamp(1.0 / playback_speed, 0.5, 2.0) + elif _audio_pitch_effect.pitch_scale != 1.0: + _audio_pitch_effect.pitch_scale = 1.0 + + +func set_audio_stream(stream: int) -> void: + if not is_open(): + printerr("Video is not open!") + return + + if not stream in audio_streams: + printerr("Invalid audio stream!") + return + + if enable_audio: + _open_audio(stream) + if is_playing and audio_player.stream.get_length() != 0: + audio_player.set_stream_paused(false) + audio_player.play(current_frame / _frame_rate) + audio_player.set_stream_paused(!is_playing) + + +#------------------------------------------------ MISC +## Converts the given duration as seconds in a formatted string. (hh):mm:ss +func duration_to_formatted_string(duration_in_seconds: float) -> String: + var hours: int = floori(duration_in_seconds / 3600.0) + var minutes: int = floori(duration_in_seconds / 60.0) % 60 + var seconds: int = floori(duration_in_seconds) % 60 + + if hours == 0: + return "%02d:%02d" % [minutes, seconds] + return "%02d:%02d:%02d" % [hours, minutes, seconds] + + +func _open_video() -> void: + if video.open(path): + printerr("Error opening video!") + + +func _open_audio(stream_id: int = -1) -> void: + var stream: AudioStreamFFmpeg = AudioStreamFFmpeg.new() + + if stream.open(path, stream_id) != OK: + printerr("Failed to open AudioStreamFFmpeg for: %s" % path) + return + + audio_player.set_stream.call_deferred(stream) + + +func _print_stream_info(streams: PackedInt32Array) -> void: + for i: int in range(len(streams)): + var metadata: Dictionary = video.get_stream_metadata(streams[i]) + var title: String = metadata.get("title") + var language: String = metadata.get("language") + + if title == "": + title = "Track " + str(i + 1) + if language != "": + title += " - %s" % language + + print("- %s" % title) + + +func _print_system_debug() -> void: + print_rich("[b]System info") + print("OS name: ", OS.get_name()) + print("Distro name: ", OS.get_distribution_name()) + print("OS version: ", OS.get_version()) + print_rich("Memory info:\n\t", OS.get_memory_info()) + print("CPU name: ", OS.get_processor_name()) + print("Threads count: ", OS.get_processor_count()) + + +func _print_video_debug() -> void: + print_rich("[b]Video debug info") + print("Extension: ", path.get_extension()) + print("Resolution: ", _resolution) + print("Actual resolution: ", video.get_actual_resolution()) + print("Pixel format: ", video.get_pixel_format()) + print("Color profile: ", video.get_color_profile()) + print("Framerate: ", _frame_rate) + print("Duration (in frames): ", _frame_count) + print("Padding: ", _padding) + print("Rotation: ", _rotation) + print("Alpha: ", _has_alpha) + print("Full color range: ", video.is_full_color_range()) + print("Interlaced flag: ", video.get_interlaced()) + print("Using sws: ", video.is_using_sws()) + print("Sar: ", video.get_sar()) + + if video_streams.size() != 0: + print_rich("Video streams: [i](%s)" % video_streams.size()) + _print_stream_info(video_streams) + else: + print("No video streams found.") + + if audio_streams.size() != 0: + print_rich("Audio streams: [i](%s)" % audio_streams.size()) + _print_stream_info(audio_streams) + else: + print("No audio streams found.") + + if subtitle_streams.size() != 0: + print_rich("Subtitle streams: [i](%s)" % subtitle_streams.size()) + _print_stream_info(subtitle_streams) + else: + print("No subtitle streams found.") + + if chapters.size() != 0: + print_rich("Chapters: [i](%s)" % chapters.size()) + for i: int in range(chapters.size()): + var title: String = chapters[i].title + if title == "": + title = "Chapter " + str(i + 1) + print("- %s-%s - %s" % [ + duration_to_formatted_string(chapters[i].start), + duration_to_formatted_string(chapters[i].end), + title + ]) + else: + print("No chapters found.") + + + +class Chapter: + var start: float ## Start of the chapter in seconds. + var end: float ## End of the chapter in seconds. + var title: String + + func _init(_start: float, _end: float, _title: String) -> void: + start = _start + end = _end + title = _title diff --git a/addons/gde_gozen/video_playback.gd.uid b/addons/gde_gozen/video_playback.gd.uid new file mode 100644 index 00000000..d57915fe --- /dev/null +++ b/addons/gde_gozen/video_playback.gd.uid @@ -0,0 +1 @@ +uid://ivx7m2bfysip diff --git a/addons/gde_gozen/yuv_to_rgb.gdshader b/addons/gde_gozen/yuv_to_rgb.gdshader new file mode 100644 index 00000000..5cb98819 --- /dev/null +++ b/addons/gde_gozen/yuv_to_rgb.gdshader @@ -0,0 +1,66 @@ +shader_type canvas_item; + +uniform sampler2D y_data; +uniform sampler2D u_data; +uniform sampler2D v_data; +uniform sampler2D a_data; + +uniform vec2 resolution; +uniform vec4 color_profile; +uniform bool full_color; +uniform int interlaced; // 0 = no, 1 = top first, 2 = bottom first +uniform float rotation; + +varying vec2 tex_uv; +varying vec2 chroma_uv; +varying vec4 modulate; + +const vec3 LIMITED_Y_OFFSET = vec3(16.0/255.0, 128.0/255.0, 128.0/255.0); +const vec3 LIMITED_SCALE = vec3(255.0/219.0, 255.0/224.0, 255.0/224.0); + + + +void vertex() { + // Handling rotation in vertex + float c = cos(rotation); + float s = sin(rotation); + mat2 rot_mat = mat2(vec2(c, s), vec2(-s, c)); + + vec2 centered_uv = UV - 0.5; + vec2 rotated_uv = rot_mat * centered_uv; + + tex_uv = rotated_uv + 0.5; + chroma_uv = tex_uv; + + modulate = COLOR; +} + + +void fragment() { + if (tex_uv.x < 0.0 || tex_uv.x > 1.0 || tex_uv.y < 0.0 || tex_uv.y > 1.0) { + COLOR = vec4(0.0); + } else { + float y_val; + + // Deinterlacing by blending (slight blur, but viewable image) + if (interlaced > 0) { + float pixel_h = 1.0 / resolution.y; + float offset_dir = (interlaced == 1) ? -pixel_h : pixel_h; + vec2 offset_uv = clamp(tex_uv + vec2(0.0, offset_dir), 0.0, 1.0); + float y_neighbor = texture(y_data, offset_uv).r; + + y_val = mix(texture(y_data, tex_uv).r, y_neighbor, 0.5); + } else y_val = texture(y_data, tex_uv).r; + + vec3 yuv = vec3(y_val, texture(u_data, tex_uv).r, texture(v_data, tex_uv).r); + + if (full_color) yuv.yz -= 0.5; // Full range just needs Chroma offset + else yuv = (yuv - LIMITED_Y_OFFSET) * LIMITED_SCALE; + + COLOR = vec4( // Applying color profile and returning color + yuv.x + (yuv.z * color_profile.x), + yuv.x - (yuv.y * color_profile.y) - (yuv.z * color_profile.z), + yuv.x + (yuv.y * color_profile.w), + texture(a_data, tex_uv).r) * modulate; + } +} diff --git a/addons/gde_gozen/yuv_to_rgb.gdshader.uid b/addons/gde_gozen/yuv_to_rgb.gdshader.uid new file mode 100644 index 00000000..f893e00f --- /dev/null +++ b/addons/gde_gozen/yuv_to_rgb.gdshader.uid @@ -0,0 +1 @@ +uid://dx78avppolbai diff --git a/addons/kenny_spritesheet_importer/editor_plugin.gd b/addons/kenny_spritesheet_importer/editor_plugin.gd new file mode 100644 index 00000000..25650008 --- /dev/null +++ b/addons/kenny_spritesheet_importer/editor_plugin.gd @@ -0,0 +1,14 @@ +@tool +extends EditorPlugin + +var import_plugin = null + +func _enter_tree(): + import_plugin = preload("import_plugin.gd").new() + # Initialization of the plugin goes here. + add_import_plugin(import_plugin) + + +func _exit_tree(): + remove_import_plugin(import_plugin) + import_plugin = null diff --git a/addons/kenny_spritesheet_importer/editor_plugin.gd.uid b/addons/kenny_spritesheet_importer/editor_plugin.gd.uid new file mode 100644 index 00000000..cfdc6882 --- /dev/null +++ b/addons/kenny_spritesheet_importer/editor_plugin.gd.uid @@ -0,0 +1 @@ +uid://cshumauwb262n diff --git a/addons/kenny_spritesheet_importer/import_plugin.gd b/addons/kenny_spritesheet_importer/import_plugin.gd new file mode 100644 index 00000000..66b9a392 --- /dev/null +++ b/addons/kenny_spritesheet_importer/import_plugin.gd @@ -0,0 +1,131 @@ + +@tool +extends EditorImportPlugin + +enum Presets { DEFAULT } + +func _get_importer_name(): + return "iseesharp83.kenney.spritesheet.importer" + +func _get_visible_name(): + return "Kenney Spritesheet" + +func _get_recognized_extensions(): + return ["png"] + +func _get_priority(): + return 0.5 + +func _get_import_order(): + return 0 + +func _get_save_extension(): + return "res" + +func _get_resource_type(): + return "Resource" + +func _get_preset_count(): + return Presets.size() + +func _get_preset_name(preset_index): + match preset_index: + Presets.DEFAULT: return "Default" + +func _get_import_options(path, preset_index): + var options := [ + { + "name": "spritesheet_xml_file", + "default_value": path.replacen(".png", ".xml"), + "property_hint": PROPERTY_HINT_FILE, + "hint_string": "*.xml" + }, + { + "name": "destination_folder", + "default_value": path.get_basename(), + "property_hint": PROPERTY_HINT_DIR + }] + match preset_index: + Presets.DEFAULT: + return options + _: + return [] + +func _get_option_visibility(path, option_name, options): + return true + +func _import(source_file, save_path, options, platform_variants, gen_files): + var atlas = read_kenney_sprite_sheet(options.spritesheet_xml_file) + var folder = options.destination_folder + create_folder(folder) + + var full_image = ImageTexture.create_from_image(Image.load_from_file(source_file)) + + if not full_image: + printerr("Failed to load image file: " + source_file) + return ERR_FILE_NOT_FOUND + + create_atlas_textures(folder, full_image, atlas, gen_files) + return ResourceSaver.save(Resource.new(), "%s.%s" % [save_path, _get_save_extension()]) + +func create_folder(folder): + var dir := DirAccess.open("res://") + if not dir.dir_exists(folder): + if not dir.make_dir_recursive(folder) == OK: + printerr("Failed to create folder: " + folder) + +func create_atlas_textures(folder, full_image, atlas, gen_files): + for sprite in atlas.sprites: + if not create_atlas_texture(folder, full_image, sprite, gen_files): + return false + return true + +func create_atlas_texture(folder, full_image, sprite, gen_files): + var name = "%s/%s.%s" % [folder, sprite.name.get_basename(), "tres"] + var texture + if ResourceLoader.exists(name, "AtlasTexture"): + texture = ResourceLoader.load(name, "AtlasTexture") + else: + texture = AtlasTexture.new() + + texture.atlas = full_image + texture.region = Rect2(sprite.x, sprite.y, sprite.width, sprite.height) + gen_files.push_back(name) + return save_resource(name, texture) + +func save_resource(name, texture): + create_folder(name.get_base_dir()) + var status = ResourceSaver.save(texture, name) + if status != OK: + printerr("Failed to save resource " + name) + return false + return true + + +func read_kenney_sprite_sheet(source_file): + var atlas = null + var sprites = [] + var parser = XMLParser.new() + if OK == parser.open(source_file): + var read = parser.read() + if read == OK: + atlas = {} + atlas["sprites"] = sprites + while read != ERR_FILE_EOF: + if parser.get_node_type() == XMLParser.NODE_ELEMENT: + var node_name = parser.get_node_name() + match node_name: + "TextureAtlas": + var imagePath = parser.get_named_attribute_value("imagePath") + "SubTexture": + var sprite = {} + sprite['name'] = parser.get_named_attribute_value("name") + sprite['x'] = float(parser.get_named_attribute_value("x")) + sprite['y'] = float(parser.get_named_attribute_value("y")) + sprite['width'] = float(parser.get_named_attribute_value("width")) + sprite['height'] = float(parser.get_named_attribute_value("height")) + sprites.append(sprite) + read = parser.read() + return atlas + + diff --git a/addons/kenny_spritesheet_importer/import_plugin.gd.uid b/addons/kenny_spritesheet_importer/import_plugin.gd.uid new file mode 100644 index 00000000..0420ef9f --- /dev/null +++ b/addons/kenny_spritesheet_importer/import_plugin.gd.uid @@ -0,0 +1 @@ +uid://befkjtwvjfcfb diff --git a/addons/kenny_spritesheet_importer/plugin.cfg b/addons/kenny_spritesheet_importer/plugin.cfg new file mode 100644 index 00000000..017a60ff --- /dev/null +++ b/addons/kenny_spritesheet_importer/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Kenney Spritesheet Importer" +description="Imports 2D Spritesheets from Kenney assets. These are normally found in the Spritesheets folder, and include a png and an xml file with and nodes." +author="iseesharp83" +version="1.0.0" +script="editor_plugin.gd" diff --git a/addons/script_splitter/assets/Close.svg b/addons/script_splitter/assets/Close.svg new file mode 100644 index 00000000..be1c1dce --- /dev/null +++ b/addons/script_splitter/assets/Close.svg @@ -0,0 +1 @@ + diff --git a/addons/script_splitter/assets/Close.svg.import b/addons/script_splitter/assets/Close.svg.import new file mode 100644 index 00000000..7ba6472c --- /dev/null +++ b/addons/script_splitter/assets/Close.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4juherhkw8hp" +path="res://.godot/imported/Close.svg-9cdf2c31c5bc249987a101663dde96f2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/Close.svg" +dest_files=["res://.godot/imported/Close.svg-9cdf2c31c5bc249987a101663dde96f2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/LTabBar.svg b/addons/script_splitter/assets/LTabBar.svg new file mode 100644 index 00000000..09a66e14 --- /dev/null +++ b/addons/script_splitter/assets/LTabBar.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/addons/script_splitter/assets/LTabBar.svg.import b/addons/script_splitter/assets/LTabBar.svg.import new file mode 100644 index 00000000..ce8dd673 --- /dev/null +++ b/addons/script_splitter/assets/LTabBar.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cod0nie30hnjp" +path="res://.godot/imported/LTabBar.svg-0e9371d55bcb56b674582e235e5d8a1d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/LTabBar.svg" +dest_files=["res://.godot/imported/LTabBar.svg-0e9371d55bcb56b674582e235e5d8a1d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/RTabBar.svg b/addons/script_splitter/assets/RTabBar.svg new file mode 100644 index 00000000..9f28e6a1 --- /dev/null +++ b/addons/script_splitter/assets/RTabBar.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/addons/script_splitter/assets/RTabBar.svg.import b/addons/script_splitter/assets/RTabBar.svg.import new file mode 100644 index 00000000..c9c9fec9 --- /dev/null +++ b/addons/script_splitter/assets/RTabBar.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://di3qxejijgp0e" +path="res://.godot/imported/RTabBar.svg-1cf63dac5b4ec3a67a7cdab390c157d7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/RTabBar.svg" +dest_files=["res://.godot/imported/RTabBar.svg-1cf63dac5b4ec3a67a7cdab390c157d7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/TabBar.svg b/addons/script_splitter/assets/TabBar.svg new file mode 100644 index 00000000..b2af5815 --- /dev/null +++ b/addons/script_splitter/assets/TabBar.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/addons/script_splitter/assets/TabBar.svg.import b/addons/script_splitter/assets/TabBar.svg.import new file mode 100644 index 00000000..b2afba8f --- /dev/null +++ b/addons/script_splitter/assets/TabBar.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dceedsu8mfraw" +path="res://.godot/imported/TabBar.svg-e83cf2d6da8a41e35756355b8a0f3b2b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/TabBar.svg" +dest_files=["res://.godot/imported/TabBar.svg-e83cf2d6da8a41e35756355b8a0f3b2b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/atop.png b/addons/script_splitter/assets/atop.png new file mode 100644 index 00000000..9f417c65 Binary files /dev/null and b/addons/script_splitter/assets/atop.png differ diff --git a/addons/script_splitter/assets/atop.png.import b/addons/script_splitter/assets/atop.png.import new file mode 100644 index 00000000..2fb38de5 --- /dev/null +++ b/addons/script_splitter/assets/atop.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://r6u1jtnbr4eg" +path="res://.godot/imported/atop.png-fdfd8a2738a7960f4b5badd36ef62418.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/atop.png" +dest_files=["res://.godot/imported/atop.png-fdfd8a2738a7960f4b5badd36ef62418.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/expand.svg b/addons/script_splitter/assets/expand.svg new file mode 100644 index 00000000..c642cd73 --- /dev/null +++ b/addons/script_splitter/assets/expand.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/addons/script_splitter/assets/expand.svg.import b/addons/script_splitter/assets/expand.svg.import new file mode 100644 index 00000000..dc96ad8d --- /dev/null +++ b/addons/script_splitter/assets/expand.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cractge21enk" +path="res://.godot/imported/expand.svg-c8a40c528bdc9ff1c7945508158c04ee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/expand.svg" +dest_files=["res://.godot/imported/expand.svg-c8a40c528bdc9ff1c7945508158c04ee.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/file_in.png b/addons/script_splitter/assets/file_in.png new file mode 100644 index 00000000..0ad944ca Binary files /dev/null and b/addons/script_splitter/assets/file_in.png differ diff --git a/addons/script_splitter/assets/file_in.png.import b/addons/script_splitter/assets/file_in.png.import new file mode 100644 index 00000000..d86ef682 --- /dev/null +++ b/addons/script_splitter/assets/file_in.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxds5tr6aq5v3" +path="res://.godot/imported/file_in.png-349fab3e88dcbece78de5e93090cc9f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/file_in.png" +dest_files=["res://.godot/imported/file_in.png-349fab3e88dcbece78de5e93090cc9f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/fill_expand.svg b/addons/script_splitter/assets/fill_expand.svg new file mode 100644 index 00000000..2bd00148 --- /dev/null +++ b/addons/script_splitter/assets/fill_expand.svg @@ -0,0 +1,55 @@ + + + + diff --git a/addons/script_splitter/assets/fill_expand.svg.import b/addons/script_splitter/assets/fill_expand.svg.import new file mode 100644 index 00000000..12462f11 --- /dev/null +++ b/addons/script_splitter/assets/fill_expand.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cejhhnje48450" +path="res://.godot/imported/fill_expand.svg-82bbfe3ae93c5fe880e9ec053c7ac4d3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/fill_expand.svg" +dest_files=["res://.godot/imported/fill_expand.svg-82bbfe3ae93c5fe880e9ec053c7ac4d3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/github_CodeNameTwister.svg b/addons/script_splitter/assets/github_CodeNameTwister.svg new file mode 100644 index 00000000..8d15944d --- /dev/null +++ b/addons/script_splitter/assets/github_CodeNameTwister.svg @@ -0,0 +1,199 @@ + + + + diff --git a/addons/script_splitter/assets/github_CodeNameTwister.svg.import b/addons/script_splitter/assets/github_CodeNameTwister.svg.import new file mode 100644 index 00000000..75840036 --- /dev/null +++ b/addons/script_splitter/assets/github_CodeNameTwister.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0vjxv8swip1d" +path="res://.godot/imported/github_CodeNameTwister.svg-d4436ff9173932734b4a3b29793961a3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/github_CodeNameTwister.svg" +dest_files=["res://.godot/imported/github_CodeNameTwister.svg-d4436ff9173932734b4a3b29793961a3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/minus_row.svg b/addons/script_splitter/assets/minus_row.svg new file mode 100644 index 00000000..b0728081 --- /dev/null +++ b/addons/script_splitter/assets/minus_row.svg @@ -0,0 +1,47 @@ + + + + diff --git a/addons/script_splitter/assets/minus_row.svg.import b/addons/script_splitter/assets/minus_row.svg.import new file mode 100644 index 00000000..53f2f5e8 --- /dev/null +++ b/addons/script_splitter/assets/minus_row.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cy5kd5rqu50mo" +path="res://.godot/imported/minus_row.svg-544028da056c25554a7fabef78831360.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/minus_row.svg" +dest_files=["res://.godot/imported/minus_row.svg-544028da056c25554a7fabef78831360.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/pin.svg b/addons/script_splitter/assets/pin.svg new file mode 100644 index 00000000..010cf261 --- /dev/null +++ b/addons/script_splitter/assets/pin.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/addons/script_splitter/assets/pin.svg.import b/addons/script_splitter/assets/pin.svg.import new file mode 100644 index 00000000..efcb74b1 --- /dev/null +++ b/addons/script_splitter/assets/pin.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7rel5pr2g7d2" +path="res://.godot/imported/pin.svg-fc2020fc902e599cc597f694f431fc67.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/pin.svg" +dest_files=["res://.godot/imported/pin.svg-fc2020fc902e599cc597f694f431fc67.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/plus_row.svg b/addons/script_splitter/assets/plus_row.svg new file mode 100644 index 00000000..c5e6934e --- /dev/null +++ b/addons/script_splitter/assets/plus_row.svg @@ -0,0 +1,41 @@ + + + + diff --git a/addons/script_splitter/assets/plus_row.svg.import b/addons/script_splitter/assets/plus_row.svg.import new file mode 100644 index 00000000..66ddf74d --- /dev/null +++ b/addons/script_splitter/assets/plus_row.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cj6sg45m64fiv" +path="res://.godot/imported/plus_row.svg-c644e3f245eec8d807df6e16bc34686c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/plus_row.svg" +dest_files=["res://.godot/imported/plus_row.svg-c644e3f245eec8d807df6e16bc34686c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/sep_bottom.svg b/addons/script_splitter/assets/sep_bottom.svg new file mode 100644 index 00000000..eba223fe --- /dev/null +++ b/addons/script_splitter/assets/sep_bottom.svg @@ -0,0 +1,49 @@ + + + + diff --git a/addons/script_splitter/assets/sep_bottom.svg.import b/addons/script_splitter/assets/sep_bottom.svg.import new file mode 100644 index 00000000..0d61a2fc --- /dev/null +++ b/addons/script_splitter/assets/sep_bottom.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dt830c42xmul5" +path="res://.godot/imported/sep_bottom.svg-61090b97968673a2d1c2316fdbd96a52.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/sep_bottom.svg" +dest_files=["res://.godot/imported/sep_bottom.svg-61090b97968673a2d1c2316fdbd96a52.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/sep_left.svg b/addons/script_splitter/assets/sep_left.svg new file mode 100644 index 00000000..da633196 --- /dev/null +++ b/addons/script_splitter/assets/sep_left.svg @@ -0,0 +1,50 @@ + + + + diff --git a/addons/script_splitter/assets/sep_left.svg.import b/addons/script_splitter/assets/sep_left.svg.import new file mode 100644 index 00000000..63ccad2a --- /dev/null +++ b/addons/script_splitter/assets/sep_left.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wp32fwv4flno" +path="res://.godot/imported/sep_left.svg-a3dbf802dac574852ac4942fe902046b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/sep_left.svg" +dest_files=["res://.godot/imported/sep_left.svg-a3dbf802dac574852ac4942fe902046b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/sep_right.svg b/addons/script_splitter/assets/sep_right.svg new file mode 100644 index 00000000..03cf77f2 --- /dev/null +++ b/addons/script_splitter/assets/sep_right.svg @@ -0,0 +1,50 @@ + + + + diff --git a/addons/script_splitter/assets/sep_right.svg.import b/addons/script_splitter/assets/sep_right.svg.import new file mode 100644 index 00000000..8d230a87 --- /dev/null +++ b/addons/script_splitter/assets/sep_right.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1ee2fk3y8n83" +path="res://.godot/imported/sep_right.svg-a91956bf42da3469be85043a09fd95d6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/sep_right.svg" +dest_files=["res://.godot/imported/sep_right.svg-a91956bf42da3469be85043a09fd95d6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/sep_up.svg b/addons/script_splitter/assets/sep_up.svg new file mode 100644 index 00000000..ba0b5bf0 --- /dev/null +++ b/addons/script_splitter/assets/sep_up.svg @@ -0,0 +1,49 @@ + + + + diff --git a/addons/script_splitter/assets/sep_up.svg.import b/addons/script_splitter/assets/sep_up.svg.import new file mode 100644 index 00000000..8dda0336 --- /dev/null +++ b/addons/script_splitter/assets/sep_up.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cffqinddvan8k" +path="res://.godot/imported/sep_up.svg-e6cbe5b85256148fcb7520abb21a4912.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/sep_up.svg" +dest_files=["res://.godot/imported/sep_up.svg-e6cbe5b85256148fcb7520abb21a4912.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_cminus.svg b/addons/script_splitter/assets/split_cminus.svg new file mode 100644 index 00000000..8981386d --- /dev/null +++ b/addons/script_splitter/assets/split_cminus.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + split-screen + + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_cminus.svg.import b/addons/script_splitter/assets/split_cminus.svg.import new file mode 100644 index 00000000..38ce26a0 --- /dev/null +++ b/addons/script_splitter/assets/split_cminus.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1b7aoplxr0x7" +path="res://.godot/imported/split_cminus.svg-824cba903437f8f9e5c3e4d4b77f9d32.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_cminus.svg" +dest_files=["res://.godot/imported/split_cminus.svg-824cba903437f8f9e5c3e4d4b77f9d32.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/assets/split_cminus_tool.svg b/addons/script_splitter/assets/split_cminus_tool.svg new file mode 100644 index 00000000..9c7b077b --- /dev/null +++ b/addons/script_splitter/assets/split_cminus_tool.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + split-screen + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_cminus_tool.svg.import b/addons/script_splitter/assets/split_cminus_tool.svg.import new file mode 100644 index 00000000..56107924 --- /dev/null +++ b/addons/script_splitter/assets/split_cminus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3s0q8570ppjv" +path="res://.godot/imported/split_cminus_tool.svg-f99ac5c50ace36891a5cb7eabb24b1e1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_cminus_tool.svg" +dest_files=["res://.godot/imported/split_cminus_tool.svg-f99ac5c50ace36891a5cb7eabb24b1e1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_cplus.svg b/addons/script_splitter/assets/split_cplus.svg new file mode 100644 index 00000000..2cc4a56c --- /dev/null +++ b/addons/script_splitter/assets/split_cplus.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + split-screen + + + + + + + + split-screen + + + + diff --git a/addons/script_splitter/assets/split_cplus.svg.import b/addons/script_splitter/assets/split_cplus.svg.import new file mode 100644 index 00000000..2525f80c --- /dev/null +++ b/addons/script_splitter/assets/split_cplus.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvyjqo1dus1xu" +path="res://.godot/imported/split_cplus.svg-ebac7c53bed9debcb76e6d75b7c90c62.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_cplus.svg" +dest_files=["res://.godot/imported/split_cplus.svg-ebac7c53bed9debcb76e6d75b7c90c62.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/assets/split_cplus_tool.svg b/addons/script_splitter/assets/split_cplus_tool.svg new file mode 100644 index 00000000..4e76e51d --- /dev/null +++ b/addons/script_splitter/assets/split_cplus_tool.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + split-screen + + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_cplus_tool.svg.import b/addons/script_splitter/assets/split_cplus_tool.svg.import new file mode 100644 index 00000000..bf6e9416 --- /dev/null +++ b/addons/script_splitter/assets/split_cplus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6pq6vesk5ycg" +path="res://.godot/imported/split_cplus_tool.svg-7b219b19dedaa176071ebc738b2f9a3a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_cplus_tool.svg" +dest_files=["res://.godot/imported/split_cplus_tool.svg-7b219b19dedaa176071ebc738b2f9a3a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_minus_tool.svg b/addons/script_splitter/assets/split_minus_tool.svg new file mode 100644 index 00000000..68ee0b2d --- /dev/null +++ b/addons/script_splitter/assets/split_minus_tool.svg @@ -0,0 +1,68 @@ + + + + diff --git a/addons/script_splitter/assets/split_minus_tool.svg.import b/addons/script_splitter/assets/split_minus_tool.svg.import new file mode 100644 index 00000000..96e42dc3 --- /dev/null +++ b/addons/script_splitter/assets/split_minus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btm5y6ifmbles" +path="res://.godot/imported/split_minus_tool.svg-ba276ee9ec33f679733c2cb056acafa5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_minus_tool.svg" +dest_files=["res://.godot/imported/split_minus_tool.svg-ba276ee9ec33f679733c2cb056acafa5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_plus_tool.svg b/addons/script_splitter/assets/split_plus_tool.svg new file mode 100644 index 00000000..82ae1488 --- /dev/null +++ b/addons/script_splitter/assets/split_plus_tool.svg @@ -0,0 +1,56 @@ + + + + diff --git a/addons/script_splitter/assets/split_plus_tool.svg.import b/addons/script_splitter/assets/split_plus_tool.svg.import new file mode 100644 index 00000000..6cdcff5f --- /dev/null +++ b/addons/script_splitter/assets/split_plus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg17j5ifvqsi8" +path="res://.godot/imported/split_plus_tool.svg-8993d76480b248c74e5ceef08c49f018.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_plus_tool.svg" +dest_files=["res://.godot/imported/split_plus_tool.svg-8993d76480b248c74e5ceef08c49f018.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_rminus.svg b/addons/script_splitter/assets/split_rminus.svg new file mode 100644 index 00000000..50fa1a29 --- /dev/null +++ b/addons/script_splitter/assets/split_rminus.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + split-screen + + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_rminus.svg.import b/addons/script_splitter/assets/split_rminus.svg.import new file mode 100644 index 00000000..7fe35a3d --- /dev/null +++ b/addons/script_splitter/assets/split_rminus.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvjk4po8w2m2j" +path="res://.godot/imported/split_rminus.svg-bf378fc258a09d6e64dbccad6e9eee51.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_rminus.svg" +dest_files=["res://.godot/imported/split_rminus.svg-bf378fc258a09d6e64dbccad6e9eee51.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/assets/split_rminus_tool.svg b/addons/script_splitter/assets/split_rminus_tool.svg new file mode 100644 index 00000000..471eb434 --- /dev/null +++ b/addons/script_splitter/assets/split_rminus_tool.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + split-screen + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_rminus_tool.svg.import b/addons/script_splitter/assets/split_rminus_tool.svg.import new file mode 100644 index 00000000..bffc387d --- /dev/null +++ b/addons/script_splitter/assets/split_rminus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1yp7jd6hvabl" +path="res://.godot/imported/split_rminus_tool.svg-d7a9fa38184b6bc3008e9a11b0f95fb6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_rminus_tool.svg" +dest_files=["res://.godot/imported/split_rminus_tool.svg-d7a9fa38184b6bc3008e9a11b0f95fb6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/assets/split_rplus.svg b/addons/script_splitter/assets/split_rplus.svg new file mode 100644 index 00000000..81e2b3a6 --- /dev/null +++ b/addons/script_splitter/assets/split_rplus.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + split-screen + + + + + + split-screen + + + + + + diff --git a/addons/script_splitter/assets/split_rplus.svg.import b/addons/script_splitter/assets/split_rplus.svg.import new file mode 100644 index 00000000..4ef0b8fd --- /dev/null +++ b/addons/script_splitter/assets/split_rplus.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1sqajw0mikdj" +path="res://.godot/imported/split_rplus.svg-83f950338217237725eab50ab336295c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_rplus.svg" +dest_files=["res://.godot/imported/split_rplus.svg-83f950338217237725eab50ab336295c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/assets/split_rplus_tool.svg b/addons/script_splitter/assets/split_rplus_tool.svg new file mode 100644 index 00000000..7e7967d4 --- /dev/null +++ b/addons/script_splitter/assets/split_rplus_tool.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + split-screen + + + + + + split-screen + + + + + diff --git a/addons/script_splitter/assets/split_rplus_tool.svg.import b/addons/script_splitter/assets/split_rplus_tool.svg.import new file mode 100644 index 00000000..803bef8b --- /dev/null +++ b/addons/script_splitter/assets/split_rplus_tool.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://den018oksgm5m" +path="res://.godot/imported/split_rplus_tool.svg-850c12c31af7c3823ebfb1d707c5d314.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/assets/split_rplus_tool.svg" +dest_files=["res://.godot/imported/split_rplus_tool.svg-850c12c31af7c3823ebfb1d707c5d314.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/script_splitter/core/Input.gd b/addons/script_splitter/core/Input.gd new file mode 100644 index 00000000..6567a055 --- /dev/null +++ b/addons/script_splitter/core/Input.gd @@ -0,0 +1,201 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +const Builder = preload("./../core/builder.gd") +const Context = preload("./../core/contex/window.gd") +const SSPContext = preload("./../core/contex/ssp_window.gd") + +var _plugin : EditorPlugin = null +var _builder : Builder = null + +signal add_row(value : Resource) +signal add_column(value : Resource) +signal remove_row(value : Resource) +signal remove_column(value : Resource) + +signal left_tab_close(value : Resource) +signal right_tab_close(value : Resource) +signal others_tab_close(value : Resource) + +const ICON_ADD_COLUMN : Texture2D = preload("./../assets/split_cplus.svg") +const ICON_ADD_ROW : Texture2D = preload("./../assets/split_rplus.svg") +const ICON_REMOVE_COLUMN : Texture2D = preload("./../assets/split_cminus.svg") +const ICON_REMOVE_ROW : Texture2D = preload("./../assets/split_rminus.svg") + +const L_TAB_BAR : Texture2D = preload("./../assets/LTabBar.svg") +const R_TAB_BAR : Texture2D = preload("./../assets/RTabBar.svg") +const TAB_BAR: Texture2D = preload("./../assets/TabBar.svg") + + +var _context_add_split_column : Context = null +var _context_add_split_row : Context = null +var _context_remove_split_column : Context = null +var _context_remove_split_row : Context = null +var _context_editor_split : SSPContext = null + +var _editor_context_add_split_column : Context = null +var _editor_context_add_split_row : Context = null +var _editor_context_remove_split_column : Context = null +var _editor_context_remove_split_row : Context = null + +var _editor_context_left_tab_close : Context = null +var _editor_context_right_tab_close : Context = null +var _editor_context_botH_tab_close : Context = null + +func get_honey_splitter() -> SSPContext: + return _context_editor_split + +# Traduction? +func _tr(message : String) -> String: + # ... + return message.capitalize() + +func init_1() -> void: + + _context_add_split_column = Context.new(_tr("SPLIT_COLUMN"), _add_column_split, _can_split, ICON_ADD_COLUMN) + _context_add_split_row = Context.new(_tr("SPLIT_ROW"), _add_row_split, _can_split, ICON_ADD_ROW) + _context_remove_split_column = Context.new(_tr("MERGE_SPLITTED_COLUMN"), _remove_column_split, _can_merge_column, ICON_REMOVE_COLUMN) + _context_remove_split_row = Context.new(_tr("MERGE_SPLITTED_ROW"), _remove_row_split, _can_merge_row, ICON_REMOVE_ROW) + _context_editor_split = SSPContext.new() + + _editor_context_add_split_column = Context.new(_tr("SPLIT_COLUMN"), _add_column_split, _can_split, ICON_ADD_COLUMN) + _editor_context_add_split_row = Context.new(_tr("SPLIT_ROW"), _add_row_split, _can_split, ICON_ADD_ROW) + _editor_context_remove_split_column = Context.new(_tr("MERGE_SPLITTED_COLUMN"), _remove_column_split, _can_merge_column, ICON_REMOVE_COLUMN) + _editor_context_remove_split_row = Context.new(_tr("MERGE_SPLITTED_ROW"), _remove_row_split, _can_merge_row, ICON_REMOVE_ROW) + + _editor_context_left_tab_close = Context.new(_tr("CLOSE_LEFT_TABS"), _left_tab_close, _can_left_tab_close, L_TAB_BAR) + _editor_context_botH_tab_close = Context.new(_tr("CLOSE_OTHERS_TABS"), _others_tab_close, _can_others_tab_close, TAB_BAR) + _editor_context_right_tab_close = Context.new(_tr("CLOSE_RIGHT_TABS"), _right_tab_close, _can_right_tab_close, R_TAB_BAR) + + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _context_add_split_column) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _context_add_split_row) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _context_remove_split_column) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _context_remove_split_row) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _context_editor_split) + + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_add_split_column) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_add_split_row) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_remove_split_column) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_remove_split_row) + + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_left_tab_close) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_right_tab_close) + _plugin.add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_botH_tab_close) + + + +func _get_value(value : Variant) -> PackedStringArray: + if value is PackedStringArray: + return value + + elif value is Array: + var packed : PackedStringArray = [] + for x : Variant in value: + if x is Resource: + packed.append(x.resource_path) + return packed + elif x is String: + packed.append(x) + return packed + + elif value is Resource: + var packed : PackedStringArray = [value.resource_path] + return packed + return [] + +func _get_resource(value : Variant) -> Variant: + if value is Resource: + return value + elif value is Node: + return value + + var packed : PackedStringArray = [] + if value is Array: + for x : Variant in value: + if x is String: + packed.append(x) + break + elif value is PackedStringArray: + packed = value + + if packed.size() == 0: + return null + + return packed[0] + +func _can_split(value : Variant = null) -> bool: + return _plugin.builder.can_split(_get_value(value)) + +func _can_merge_column(value : Variant = null) -> bool: + return _plugin.builder.can_merge_column(_get_value(value)) + +func _can_merge_row(value : Variant = null) -> bool: + return _plugin.builder.can_merge_row(_get_value(value)) + +func _can_left_tab_close(value : Variant = null) -> bool: + return _plugin.builder.can_left_tab_close(_get_value(value)) + +func _can_right_tab_close(value : Variant = null) -> bool: + return _plugin.builder.can_right_tab_close(_get_value(value)) + +func _can_others_tab_close(value : Variant = null) -> bool: + return _plugin.builder.can_others_tab_close(_get_value(value)) + +func _left_tab_close(value : Variant = null) -> void: + left_tab_close.emit(_get_resource(value)) + +func _right_tab_close(value : Variant = null) -> void: + right_tab_close.emit(_get_resource(value)) + +func _others_tab_close(value : Variant = null) -> void: + others_tab_close.emit(_get_resource(value)) + +func _add_column_split(value : Variant = null) -> void: + add_column.emit(_get_resource(value)) + +func _add_row_split(value : Variant = null) -> void: + add_row.emit(_get_resource(value)) + +func _remove_column_split(value : Variant = null) -> void: + remove_column.emit(_get_resource(value)) + +func _remove_row_split(value : Variant = null) -> void: + remove_row.emit(_get_resource(value)) + +func init_0() -> void: + for x : Variant in [ + _context_add_split_column, + _context_add_split_row, + _context_remove_split_column, + _context_remove_split_row, + _context_editor_split, + _editor_context_add_split_column, + _editor_context_add_split_row, + _editor_context_remove_split_column, + _editor_context_remove_split_row + ]: + if is_instance_valid(x): + _plugin.remove_context_menu_plugin(x) + +func _init(plugin : EditorPlugin, builder : Builder) -> void: + _plugin = plugin + _builder = builder + +func event(event : InputEvent) -> bool: + if event.is_pressed(): + if event is InputEventKey: + if event.keycode == KEY_1 and event.ctrl_pressed: + _plugin.builder.multi_split(2, false) + pass + if event.keycode == KEY_2 and event.ctrl_pressed: + _plugin.builder.multi_split(4, false) + pass + return false diff --git a/addons/script_splitter/core/Input.gd.uid b/addons/script_splitter/core/Input.gd.uid new file mode 100644 index 00000000..b39dcb4d --- /dev/null +++ b/addons/script_splitter/core/Input.gd.uid @@ -0,0 +1 @@ +uid://dxipxeq42djlp diff --git a/addons/script_splitter/core/base/container.gd b/addons/script_splitter/core/base/container.gd new file mode 100644 index 00000000..4d4fded2 --- /dev/null +++ b/addons/script_splitter/core/base/container.gd @@ -0,0 +1,205 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const SplitterContainer = preload("./../../../script_splitter/core/ui/splitter/splitter_container.gd") +const NControl = preload("./../../core/util/control.gd") + +const IoBar = preload("./../../core/ui/splitter/io/io_bar.gd") + +signal update() +signal focus_by_tab(root : TabContainer, index : int) +signal remove_by_tab(root : TabContainer, index : int) +signal change_container(container : TabContainer) +signal exiting() + +@warning_ignore("unused_signal") +signal rmb_click(index : int, TabContainer) + +@warning_ignore("unused_signal") +signal swap_tab(from : Container, index : int, to : Container) +@warning_ignore("unused_signal") +signal same_swap_tab(from : Container, index : int, type : StringName) + +var _editor_container : TabContainer = null +var _editor_splitter_container : SplitterContainer = null + +var _current_container : TabContainer = null: + set(e): + if _current_container != e: + change_container.emit(e) + _current_container = e + +var _frm : int = 0 + +var _io_bar : Node = null + +func on_focus(root : TabContainer, index : int) -> void: + focus_by_tab.emit(root, index) + +func on_remove(root : TabContainer, index : int) -> void: + remove_by_tab.emit(root, index) + +func get_io_bar() -> IoBar: + if !is_instance_valid(_io_bar): + _io_bar = IoBar.new() + return _io_bar + +func get_container(control : Control) -> Container: + if control is SplitterContainer.SplitterEditorContainer.Editor: + return _editor_splitter_container.get_base_container(control) + return null + +func get_container_item(control : Control) -> Control: + if control is SplitterContainer.SplitterEditorContainer.Editor: + return _editor_splitter_container.get_base_container_item(control) + return null + +func _init(container : TabContainer) -> void: + _editor_container = container + _editor_splitter_container = SplitterContainer.new() + _editor_splitter_container.initialize(_editor_container, self) + _editor_splitter_container.visible = false + + _editor_container.child_entered_tree.connect(_on_update) + _editor_container.child_exiting_tree.connect(_on_update) + + _editor_container.tree_exiting.connect(_on_exiting) + + +func is_active() -> bool: + if _frm > 0: + _frm -= 1 + return false + return is_instance_valid(_editor_container) and _editor_container.is_inside_tree() + +func _on_exiting() -> void: + _frm = 3 + exiting.emit() + +func initialize_editor_container() -> void: + _editor_splitter_container.initialize_editor_contianer() + +func _on_update(__ : Node) -> void: + update.emit() + +func set_current_container(container : TabContainer) -> void: + if _editor_splitter_container.set_current_editor(container): + _current_container = container + +func get_editor_container() -> TabContainer: + return _editor_container + +func get_root_container() -> SplitterContainer.SplitterRoot: + return _editor_splitter_container.get_root() + +func get_editor_root_container(node : Node) -> SplitterContainer.BaseContainerItem: + if node is SplitterContainer.SplitterRoot: + node = node.get_parent() + return node + return null + +func get_editors() -> Array[Node]: + return _editor_container.get_children() + +func get_current_editor() -> Control: + return _editor_splitter_container.get_current_editor() + +func tool_created() -> void: + _editor_container.visible = false + _editor_splitter_container.visible = true + +func new_column() -> Control: + _current_container = _editor_splitter_container.create_new_column() + return _current_container + +func new_row() -> Control: + _current_container = _editor_splitter_container.create_new_row() + return _current_container + +func update_split_container() -> void: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__ST_CS__"): + if x.has_method(&"update"): + x.call(&"update") + +func get_all_containers() -> Array[Node]: + if !_editor_splitter_container: + return [] + return _editor_splitter_container.get_tree().get_nodes_in_group(&"__SP_BR__") + +func get_current_containers() -> Array[Node]: + if !is_instance_valid(_current_container): + return [] + var c : Control = _editor_splitter_container.get_base_container(_current_container) + if is_instance_valid(c): + return c.get_children() + return [] + +func get_all_splitters() -> Array[Node]: + if !_editor_splitter_container: + return [] + return _editor_splitter_container.get_tree().get_nodes_in_group(&"__SC_SPLITTER__") + +func get_current_splitters() -> Array[Node]: + if !is_instance_valid(_current_container): + return [] + var c : Control = _editor_splitter_container.get_base_container_item(_current_container) + if is_instance_valid(c): + c = c.get_parent() + if c: + return c.get_children() + return [] + +func garbage() -> void: + var control : Node = get_current_editor() + + var nodes : Array[Node] = get_all_splitters() + var total : int = nodes.size() + if total > 2: + total = 0 + for x : Node in nodes: + if !x.is_queued_for_deletion(): + total += 1 + + if total > 1: + for x : Node in nodes: + if total < 2: + break + if x.get_child_count() == 0: + if control == x: + control = null + if !x.is_queued_for_deletion(): + x.queue_free() + total -= 1 + + if control == null: + for x : Node in _editor_splitter_container.get_tree().get_nodes_in_group(&"__SC_SPLITTER__"): + if x is Control and !x.is_queued_for_deletion(): + control = x + break + +func reset() -> void: + _editor_container.visible = true + + if _editor_container.child_entered_tree.is_connected(_on_update): + _editor_container.child_entered_tree.disconnect(_on_update) + if _editor_container.child_exiting_tree.is_connected(_on_update): + _editor_container.child_exiting_tree.disconnect(_on_update) + + _editor_splitter_container.reset() + _editor_splitter_container.queue_free() + +func get_current_container() -> TabContainer: + return _current_container + +func move_container(from : int, to : int) -> bool: + if _editor_container.get_child_count() > from and from > -1: + _editor_container.move_child(_editor_container.get_child(from), to) + return true + return false diff --git a/addons/script_splitter/core/base/container.gd.uid b/addons/script_splitter/core/base/container.gd.uid new file mode 100644 index 00000000..a26a995c --- /dev/null +++ b/addons/script_splitter/core/base/container.gd.uid @@ -0,0 +1 @@ +uid://iyslc58y0lp1 diff --git a/addons/script_splitter/core/base/list.gd b/addons/script_splitter/core/base/list.gd new file mode 100644 index 00000000..e02e8c12 --- /dev/null +++ b/addons/script_splitter/core/base/list.gd @@ -0,0 +1,311 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const SplitterList = preload("./../../core/ui/splitter/splitter_list.gd") + +signal item_selected(item : int) +signal move_item(from : int, to : int) +signal updated() + +var _editor_list : ItemList = null +var _script_list : ItemList = null +var _script_filesearch : LineEdit = null +var _editor_filesearch : LineEdit = null +var _update_list_queue : bool = false +var _array_list : Array = [] +var _selet_queue : int = -1 +var _selecting : bool = false + +var update_selections_callback : Callable + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + if is_instance_valid(_editor_list): + _editor_list.visible = true + if _editor_list.item_selected.is_connected(_item_selected): + _editor_list.item_selected.disconnect(_item_selected) + if _editor_list.property_list_changed.is_connected(_on_property): + _editor_list.property_list_changed.disconnect(_on_property) + if is_instance_valid(_editor_filesearch): + _editor_filesearch.visible = true + + if is_instance_valid(_script_filesearch): + _script_filesearch.queue_free() + + if is_instance_valid(_script_list): + _script_list.queue_free() + +func _on_sc_item_selected(index : int) -> void: + if _script_list.item_count > index and index > -1: + index = _get_script_selected(index) + if index == -1: + return + select(index) + +func _on_sc_item_activate(index : int) -> void: + if _script_list.item_count > index: + index = _get_script_selected(index) + if index > -1 and index < _editor_list.item_count: + _editor_list.item_activated.emit(index) + +func _on_property() -> void: + _script_list.update() + +func _on_sc_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void: + if _script_list.item_count > index: + index = _get_script_selected(index) + if index == -1: + return + _editor_list.item_clicked.emit(index, at_position, mouse_button_index) + _script_list.update() + +func _get_script_selected(index : int) -> int: + if _editor_list.item_count == _script_list.item_count: + return index + + var tp : String = _script_list.get_item_tooltip(index) + var cindx : int = -1 + if !tp.is_empty(): + for x : int in _editor_list.item_count: + if tp == _editor_list.get_item_tooltip(x): + cindx = x + break + else: + tp = _script_list.get_item_text(index) + for x : int in _editor_list.item_count: + if tp == _editor_list.get_item_text(x): + cindx = x + break + + return cindx + +#func set_handler(manager : Object) -> void: + #_script_list.set_handler(manager) + # +func _init(list : ItemList) -> void: + _editor_list = list + _editor_list.item_selected.connect(_item_selected) + _editor_list.property_list_changed.connect(_on_property) + + var parent: Node = _editor_list.get_parent() + _script_list = list.duplicate() + _script_list.set_script(SplitterList) + _script_list.set_reference(_update_list) + _script_list.set_list(_editor_list) + _script_list.item_selected.connect(_on_sc_item_selected) + _script_list.item_activated.connect(_on_sc_item_activate) + _script_list.item_clicked.connect(_on_sc_item_clicked) + + if _script_list.has_signal(&"move_item_by_index"): + _script_list.connect(&"move_item_by_index", _on_move_item_by_index) + #_editor_list.draw.connect(_on_update_list) + + _script_list.add_to_group(&"__SP_LT__") + _array_list = [_editor_list, _script_list] + + list.visible = false + + var filesearch : Object = parent.get_child(0) + if filesearch is LineEdit: + _editor_filesearch = filesearch + var txt : String = filesearch.text + if !txt.is_empty(): + filesearch.set(&"text", "") + + _script_filesearch = filesearch.duplicate() + _script_filesearch.text_changed.connect(_on_update_list_search) + + filesearch.visible = false + + parent.add_child(_script_list) + parent.move_child(_script_list, 0) + parent.add_child(_script_filesearch) + parent.move_child(_script_filesearch, 0) + + _script_list.update() + +func _on_update_list() -> void: + if _update_list_queue: + return + + if !is_instance_valid(_script_list) or !is_instance_valid(_editor_list): + return + + _update_list_queue = true + + var filtered : bool = false + + if is_instance_valid(_script_filesearch): + filtered = !_script_filesearch.text.is_empty() + + + var item_list : ItemList = _editor_list + + _script_list.clear() + + if filtered: + _on_update_list_search(_script_filesearch.text) + else: + for x : int in item_list.item_count: + var indx : int = _script_list.item_count + _script_list.add_item(item_list.get_item_text(x), item_list.get_item_icon(x), true) + _script_list.set_item_metadata(indx, item_list.get_item_metadata(x)) + _script_list.set_item_tooltip(indx, item_list.get_item_tooltip(x)) + _script_list.set_item_icon_modulate(indx, item_list.get_item_icon_modulate(x)) + _script_list.set_item_custom_fg_color(indx, item_list.get_item_custom_fg_color(x)) + + update_list_selection() + + set_deferred(&"_update_list_queue", false) + +func _on_update_list_search(txt : String) -> void: + if txt.is_empty(): + _on_update_list() + return + + if !is_instance_valid(_script_list): + return + + _script_list.clear() + + var rgx : RegEx = RegEx.create_from_string("(?i).*{0}.*".format([txt])) + + if !is_instance_valid(rgx) or !rgx.is_valid(): + return + + var item_list : ItemList = _editor_list + for x : int in item_list.item_count: + var _txt : String = item_list.get_item_text(x) + if rgx.search(_txt) != null: + var indx : int = _script_list.add_item(item_list.get_item_text(x), item_list.get_item_icon(x), true) + _script_list.set_item_metadata(indx, item_list.get_item_metadata(x)) + _script_list.set_item_tooltip(indx, item_list.get_item_tooltip(x)) + _script_list.set_item_icon_modulate(indx, item_list.get_item_icon_modulate(x)) + _script_list.set_item_custom_fg_color(indx, item_list.get_item_custom_fg_color(x)) + + update_list_selection() + +func update_list_selection() -> void: + if update_selections_callback.is_valid(): + update_selections_callback.call(_array_list) + +func _item_selected(i : int) -> void: + item_selected.emit(i) + +func _update_list() -> void: + updated.emit() + _on_update_list() + +func get_editor_list() -> ItemList: + return _editor_list + +func get_selected_id() -> int: + for x : int in range(_editor_list.item_count): + if _editor_list.is_selected(x): + return x + return -1 + +func remove(index : int) -> void: + if _editor_list.item_count > index and index > -1: + _editor_list.item_clicked.emit(index, _editor_list.get_local_mouse_position(), MOUSE_BUTTON_MIDDLE) + +func item_count() -> int: + return _editor_list.item_count + +func _select() -> void: + if _selet_queue > -1 and _editor_list.item_count > _selet_queue: + _editor_list.select(_selet_queue, true) + _editor_list.item_selected.emit(_selet_queue) + _update_list.call_deferred() + _selecting = false + +func update_list() -> void: + _on_update_list() + +func select(i : int) -> void: + if i > -1 and _editor_list.item_count > i: + _selet_queue = i + if _selecting: + return + _selecting = true + _select.call_deferred() + +func is_selected(i : int) -> bool: + if _editor_list.item_count > i and i > -1: + return _editor_list.is_selected(i) + return false + +func get_item_tooltip(item : int) -> String: + if _editor_list.item_count > item and item > -1: + return _editor_list.get_item_tooltip(item) + return "" + +func get_item_icon(item : int) -> Texture2D: + if _editor_list.item_count > item and item > -1: + return _editor_list.get_item_icon(item) + return null + +func get_item_icon_modulate(item : int) -> Color: + if _editor_list.item_count > item and item > -1: + return _editor_list.get_item_icon_modulate(item) + return Color.WHITE + +func get_item_text(item : int) -> String: + if _editor_list.item_count > item and item > -1: + return _editor_list.get_item_text(item) + return "" + +func reset() -> void: + if is_instance_valid(_editor_list): + _editor_list.visible = true + if _editor_list.draw.is_connected(_on_update_list): + _editor_list.draw.disconnect(_on_update_list) + if _editor_list.item_selected.is_connected(_item_selected): + _editor_list.item_selected.disconnect(_item_selected) + if _editor_list.property_list_changed.is_connected(_on_property): + _editor_list.property_list_changed.disconnect(_on_property) + + if is_instance_valid(_editor_filesearch): + _editor_filesearch.visible = true + + if is_instance_valid(_script_filesearch): + _script_filesearch.queue_free() + + if is_instance_valid(_script_list): + _script_list.queue_free() + +func _on_move_item_by_index(from : int, to : int) -> void: + if from == to: + return + + for x : ItemList in [_script_list, _editor_list]: + if !is_instance_valid(x): + return + for y : int in [from, to]: + if x.item_count <= y or y < 0: + return + + var values : Array[int] = [from, to] + + for v : int in range(0, values.size(), 1): + if _script_list.get_item_tooltip(v) != _editor_list.get_item_tooltip(v): + var value = -1 + var st : String = _script_list.get_item_tooltip(from) + + for x : int in _editor_list.item_count: + if st == _editor_list.get_item_tooltip(x): + value = x + break + + if value == -1: + return + + values[v] = value + + move_item.emit(values[0], values[1]) diff --git a/addons/script_splitter/core/base/list.gd.uid b/addons/script_splitter/core/base/list.gd.uid new file mode 100644 index 00000000..2ffa5c23 --- /dev/null +++ b/addons/script_splitter/core/base/list.gd.uid @@ -0,0 +1 @@ +uid://ors5ojuayup4 diff --git a/addons/script_splitter/core/builder.gd b/addons/script_splitter/core/builder.gd new file mode 100644 index 00000000..8da09196 --- /dev/null +++ b/addons/script_splitter/core/builder.gd @@ -0,0 +1,249 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const EditorManager = preload("./../core/editor/godot/manager.gd") +const BaseContainer = preload("./../core/base/container.gd") +const BaseList = preload("./../core/base/list.gd") + +var _plugin : EditorPlugin = null +var _editor_manager : EditorManager = null + + +#region _REF_ +var _item_list : ItemList = null: + get:# + if !is_instance_valid(_item_list): + var script_editor: ScriptEditor = EditorInterface.get_script_editor() + var items : Array[Node] = script_editor.find_children("*", "ItemList", true, false) + if items.size() > 0: + _item_list = items[0] + else: + push_warning("[Script-Splitter] Can not find item list!") + return _item_list +#endregion + +func get_editor_manager() -> EditorManager: + return _editor_manager + +func handle(id : StringName) -> void: + _editor_manager.io.execute(id) + +func refresh_warnings() -> void: + _editor_manager.refresh_warnings.execute() + +func can_split(values : Variant) -> bool: + var current : Node = null + if values is PackedStringArray and values.size() > 0: + var root : Node = _plugin.get_tree().root + if root.has_node(values[0]): + current = root.get_node(values[0]) + elif values is Node: + current = values + return _editor_manager.get_current_totaL_editors(current) > 1 + +func can_merge_column(values : Variant) -> bool: + var current : Node = null + if values is PackedStringArray and values.size() > 0: + var root : Node = _plugin.get_tree().root + if root.has_node(values[0]): + current = root.get_node(values[0]) + elif values is Node: + current = values + return _editor_manager.get_current_total_splitters(current) > 1 + +func can_merge_row(_values : Variant) -> bool: + return _editor_manager.get_total_split_container(true) > 1 + +func can_left_tab_close(values : Variant) -> bool: + if values is PackedStringArray and values.size() > 0: + var root : Node = _plugin.get_tree().root + if root.has_node(values[0]): + values = root.get_node(values[0]) + else: + values = values[0] + var node : Node = _editor_manager.get_control_tool_by_current(values) + return node and node.get_index() > 0 + +func can_right_tab_close(values : Variant) -> bool: + if values is PackedStringArray and values.size() > 0: + var root : Node = _plugin.get_tree().root + if root.has_node(values[0]): + values = root.get_node(values[0]) + else: + values = values[0] + var node : Node = _editor_manager.get_control_tool_by_current(values) + return node and node.get_index() < node.get_parent().get_child_count() - 1 + +func can_others_tab_close(values : Variant) -> bool: + return can_left_tab_close(values) and can_right_tab_close(values) + +func update(_delta : float) -> void: + if _editor_manager.update(): + _plugin.set_process(false) + +func multi_split(number : int, as_row : bool) -> void: + var total : int = _editor_manager.get_current_total_splitters(null) + if total == number: + return + var container : Node = _editor_manager.get_current_root() + if !as_row: + if total < number: + number = number - total + while number > 0: + if !can_split(container): + return + _editor_manager.split_column.execute(container) + number -= 1 + else: + number = total - number + while number > 0: + if !can_merge_column(container): + return + _editor_manager.merge_tool.execute([_editor_manager.get_current_tool(container), false]) + number -= 1 + if !as_row: + if total < number: + number = number - total + while number > 0: + if !can_split(container): + return + _editor_manager.split_row.execute(container) + number -= 1 + else: + number = total - number + while number > 0: + if !can_merge_column(container): + return + _editor_manager.merge_tool.execute([_editor_manager.get_current_tool(container), true]) + number -= 1 + +func init_0() -> void: + if is_instance_valid(_editor_manager): + _editor_manager.reset() + _editor_manager = null + + var editor : ScriptEditor = EditorInterface.get_script_editor() + if editor: + if editor.editor_script_changed.is_connected(_on_change): + editor.editor_script_changed.disconnect(_on_change) + +func _on_change(__ : Variant = null) -> void: + _queue_update() + +func connect_callbacks( + on_column : Signal, + on_row : Signal, + out_column : Signal, + out_row : Signal, + left_tab_close : Signal, + right_tab_close : Signal, + others_tab_close : Signal, + + do_connect : bool = true) -> void: + for x : Array in [ + [on_column, _editor_manager.split_column.execute], + [on_row, _editor_manager.split_row.execute], + [out_column, _editor_manager.unsplit_column], + [out_row, _editor_manager.unsplit_row], + [left_tab_close, _editor_manager.left_tab_close], + [right_tab_close, _editor_manager.right_tab_close], + [others_tab_close, _editor_manager.others_tab_close] + ]: + if !x[0].is_null(): + if do_connect: + if !x[0].is_connected(x[1]): + x[0].connect(x[1]) + else: + if x[0].is_connected(x[1]): + x[0].disconnect(x[1]) +func _nws() -> void: + print("[Script Splitter] New Splitter System!\nNow use controls in toolbar for split columns and rows as you like!\nPlease provide feedback on the Github issues tab [https://github.com/CodeNameTwister/Script-Splitter]") + +func swap_by_src(from : String, to : String, as_left : bool) -> void: + _editor_manager.swap_tab.execute([from, to, as_left]) + +func reset_by_control(control : Node) -> void: + if _editor_manager: + _editor_manager.reset_by_control(control) + +func _clean_settings() -> void: + var e : EditorSettings = EditorInterface.get_editor_settings() + if e.has_setting("plugin/script_spliter/rows"): + _nws() + e.set_setting("plugin/script_spliter/rows", null) + e.set_setting("plugin/script_spliter/columns", null) + e.set_setting("plugin/script_spliter/save_rows_columns_count_on_exit", null) + e.set_setting("plugin/script_spliter/window/use_highlight_selected", null) + e.set_setting("plugin/script_spliter/window/highlight_selected_color", null) + e.set_setting("plugin/script_spliter/editor/split/reopen_last_closed_editor_on_add_split", null) + e.set_setting("plugin/script_spliter/editor/split/remember_last_used_editor_buffer_size", null) + e.set_setting("plugin/script_spliter/behavior/auto_create_split_by_config", null) + e.set_setting("plugin/script_spliter/editor/list/colorize_actives", null) + + for x : String in [ + "plugin/script_spliter/behaviour/refresh_warnings_on_save" + ,"plugin/script_spliter/editor/out_focus_color_value" + ,"plugin/script_spliter/editor/out_focus_color_enabled" + ,"plugin/script_spliter/editor/minimap_for_unfocus_window" + ,"plugin/script_spliter/editor/behaviour/expand_on_focus" + ,"plugin/script_spliter/editor/behaviour/can_expand_on_same_focus" + ,"plugin/script_spliter/editor/behaviour/smooth_expand" + ,"plugin/script_spliter/editor/behaviour/smooth_expand_time" + ,"plugin/script_spliter/editor/behaviour/swap_by_double_click_separator_button" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/handle_back_and_forward" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/history_size" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/using_as_next_and_back_tab" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/use_native_handler_when_there_are_no_more_tabs" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/backward_key_button_input" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/forward_key_button_input" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/backward_mouse_button_input" + ,"plugin/script_spliter/editor/behaviour/back_and_forward/forward_mouse_button_input" + ,"plugin/script_spliter/editor/list/selected_color" + ,"plugin/script_spliter/editor/list/others_color" + ,"plugin/script_spliter/editor/tabs/use_old_behaviour" + ,"plugin/script_spliter/line/size" + ,"plugin/script_spliter/line/color" + ,"plugin/script_spliter/line/draggable" + ,"plugin/script_spliter/line/expand_by_double_click" + ,"plugin/script_spliter/line/button/size" + ,"plugin/script_spliter/line/button/modulate" + ,"plugin/script_spliter/behavior/create_all_open_editors" + ]: + + if e.has_setting(x): + e.set_setting(x.replace("/script_spliter/", "/script_splitter/"), e.get_setting(x)) + e.set_setting(x, null) + + for x : int in range(1, 11, 1): + e.set_setting(str("plugin/script_spliter/input/split_type_" , x), null) + + #for x : int in range(1, 11, 1): + #e.set_setting(str("plugin/script_splitter/input/split_type_" , x), null) + + +func init_1(plugin : EditorPlugin, tab_container : TabContainer, item_list : ItemList) -> void: + if !is_instance_valid(plugin) or !is_instance_valid(tab_container): + printerr("Error, can`t initalize plugin, not valid references!") + return + + _clean_settings() + _plugin = plugin + _plugin.set_process(true) + + _editor_manager = EditorManager.new(BaseContainer.new(tab_container), BaseList.new(item_list)) + _editor_manager.update_request.connect(_queue_update) + + var editor : ScriptEditor = EditorInterface.get_script_editor() + if editor: + if !editor.editor_script_changed.is_connected(_on_change): + editor.editor_script_changed.connect(_on_change) + +func _queue_update() -> void: + _plugin.set_process(true) diff --git a/addons/script_splitter/core/builder.gd.uid b/addons/script_splitter/core/builder.gd.uid new file mode 100644 index 00000000..303723d7 --- /dev/null +++ b/addons/script_splitter/core/builder.gd.uid @@ -0,0 +1 @@ +uid://dge4wucvh6qnb diff --git a/addons/script_splitter/core/contex/ssp_window.gd b/addons/script_splitter/core/contex/ssp_window.gd new file mode 100644 index 00000000..75184b90 --- /dev/null +++ b/addons/script_splitter/core/contex/ssp_window.gd @@ -0,0 +1,264 @@ +@tool +extends EditorContextMenuPlugin +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4f +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const PLUS_SPLIT = preload("./../../assets/plus_row.svg") +const MINUS_SPLIT = preload("./../../assets/minus_row.svg") +const SspEditor = preload("./../../core/ui/splitter/editor/ssp_editor.gd") + +var _vsplits : Array[VSplitContainer] = [] + +func _translate(_str : String) -> String: + # ... + return _str + +func _popup_menu(_paths : PackedStringArray) -> void: + var sc : ScriptEditor = EditorInterface.get_script_editor() + + if !is_instance_valid(sc.get_current_script()): + return + + var ed : ScriptEditorBase = sc.get_current_editor() + var be : Control = ed.get_base_editor() + + + if be is CodeEdit: + if !(be.get_parent() is VSplitContainer): + add_context_menu_item(_translate("Sub-Split"), _on_sub_split, PLUS_SPLIT) + else: + add_context_menu_item(_translate("Remove Sub-Split"), _out_sub_split, MINUS_SPLIT) + +func is_handled(cnt : Node) -> bool: + return cnt is CodeEdit and cnt.get_parent() is VSplitContainer + + +func split() -> void: + _on_sub_split(null) + +func merge(value : Node) -> void: + _out_sub_split(value) + +func _out_sub_split(value : Variant = null) -> void: + var be : Control = null + if value is CodeEdit: + be = value + else: + var sc : ScriptEditor = EditorInterface.get_script_editor() + var ed : ScriptEditorBase = sc.get_current_editor() + be= ed.get_base_editor() + + + if be is CodeEdit: + if !is_handled(be): + return + + var parent : Node = be.get_parent() + var index : int = be.get_index() + + if !is_instance_valid(parent): + return + + if parent.get_child_count() > index + 1: + var c : Node = parent.get_child(index + 1) + if c is CodeEdit: + _on_focus(c, be) + + c.queue_free() + parent.remove_child(c) + else: + if index > 0 and parent.get_child_count() > index: + var c : Node = parent.get_child(index - 1) + if c is CodeEdit: + _on_focus(c, be) + + c.queue_free() + parent.remove_child(c) + + if parent.get_child_count() == 1: + var p : Node = parent.get_parent() + if p: + for y : Node in parent.get_children(): + if y.is_queued_for_deletion(): + continue + if y.has_meta(&"RM"): + continue + if y is CodeEdit: + if y.text_changed.is_connected(_on_text_change): + y.text_changed.disconnect(_on_text_change) + parent.remove_child(y) + p.add_child(y) + if p.get_child_count() > 1: + p.move_child(y, 0) + _vsplits.erase(parent) + parent.queue_free() + + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + if x.has_method(&"_io_call"): + x.call(&"_io_call", &"") + +func _on_sub_split(__ : Variant = null) -> void: + var sc : ScriptEditor = EditorInterface.get_script_editor() + var ed : ScriptEditorBase = sc.get_current_editor() + var be : Control = ed.get_base_editor() + + if be is CodeEdit: + var parent : Node = be.get_parent() + if is_handled(be) or !is_instance_valid(parent): + return + + var z : int = 0 + for x : Node in parent.get_children(): + if x is CodeEdit: + z += 1 + if z < 2: + var vsplit : VSplitContainer = null + if be.get_parent() is VSplitContainer: + vsplit = be.get_parent() + else: + vsplit = VSplitContainer.new() + var p : Node = be.get_parent() + if p: + p.remove_child(be) + + parent.add_child(vsplit) + parent.move_child(vsplit, 0) + vsplit.add_child(be) + + vsplit.size_flags_horizontal = Control.SIZE_EXPAND_FILL + vsplit.size_flags_vertical= Control.SIZE_EXPAND_FILL + + _vsplits.append(vsplit) + + var ne : CodeEdit = be.duplicate(0) + + ne.set_meta(&"RM", true) + ne.set_script(SspEditor) + ne.focus_mode = Control.FOCUS_CLICK + ne.mouse_filter = Control.MOUSE_FILTER_PASS + + ne.selecting_enabled = false + var nodes : Array[Node] = be.get_parent().get_parent().get_parent().find_children("*","MenuButton",true,false) + + for n : Node in nodes: + if n is MenuButton: + var mp : PopupMenu = n.get_popup() + if mp and "%" in (n.get_popup().get_item_text(0)): + if n.draw.is_connected(_on_update): + n.draw.disconnect(_on_update) + n.draw.connect(_on_update.bind(be,ne,n)) + + be.text_changed.connect(_on_text_change.bind(be, ne)) + + ne.focus_entered.connect(_on_focus.bind(ne, be)) + ne.gui_input.connect(_on_gui.bind(ne, be)) + + _on_text_change(be, ne) + vsplit.add_child(ne) + + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + if x.has_method(&"_io_call"): + x.call(&"_io_call", &"") + +func _on_gui(e : InputEvent, f : CodeEdit, t : CodeEdit) -> void: + if t.has_focus(): + if e.is_pressed(): + if e is InputEventMouseButton: + if e.button_index == MOUSE_BUTTON_LEFT: + return + _on_focus(f, t) + else: + if e.is_pressed(): + if e is InputEventMouseButton: + if e.button_index != MOUSE_BUTTON_RIGHT: + return + _on_focus(f, t) + + +func _on_update(f : Variant, t : Variant, r : Variant) -> void: + if is_instance_valid(f) and is_instance_valid(t): + t.set(&"theme_override_font_sizes/font_size", f.get(&"theme_override_font_sizes/font_size")) + return + if is_instance_valid(r): + if r.draw.is_connected(_on_update): + r.draw.disconnect(_on_update) + +func _on_focus(f : CodeEdit, t : CodeEdit) -> void: + if !is_instance_valid(f) or !is_instance_valid(t): + return + if f.text != t.text: + var sv : float = f.scroll_vertical + var sh : int = f.scroll_horizontal + f.set(&"text", t.text) + f.scroll_vertical = sv + f.scroll_horizontal = sh + var sv0 : float = f.scroll_vertical + var sh0 : int = f.scroll_horizontal + var sv1 : float = t.scroll_vertical + var sh1 : int = t.scroll_horizontal + t.scroll_vertical = sv0 + t.scroll_horizontal = sh0 + f.scroll_vertical = sv1 + f.scroll_horizontal = sh1 + var index : int = t.get_index() + var p : Node = f.get_parent() + p.remove_child(f) + p.add_child(f) + t.grab_focus() + + if p.get_child_count() > index or index == -1: + p.move_child(f, index) + +func _on_text_change(ca : CodeEdit, cb : CodeEdit) -> void: + if cb.has_method(&"set_text_reference"): + cb.call(&"set_text_reference", ca.text) + return + var sv : float = cb.scroll_vertical + var sh : int = cb.scroll_horizontal + cb.set(&"text", ca.text) + cb.scroll_vertical = sv + cb.scroll_horizontal = sh + +func _reorder(index : int, cd : CodeEdit, line : int, column : int) -> void: + if cd.get_caret_count() <= index: + cd.add_caret(mini(cd.get_line_count(), line), column) + return + cd.set_caret_line(mini(cd.get_line_count(), line), false, true, 0, index) + cd.set_caret_column(column, false, index) + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + for x : Node in _vsplits: + if !is_instance_valid(x): + continue + var p : Node = x.get_parent() + for y : Node in x.get_children(): + if y.is_queued_for_deletion(): + continue + if y.has_meta(&"RM"): + continue + if y is CodeEdit: + for cn : Dictionary in y.text_changed.get_connections(): + var callable : Callable = cn["callable"] + if !callable.is_valid(): + y.text_changed.disconnect(callable) + + for n : Node in x.get_parent().get_parent().get_parent().find_children("*","MenuButton",true,false): + if n is MenuButton: + var mp : PopupMenu = n.get_popup() + if mp and "%" in (n.get_popup().get_item_text(0)): + for cn : Dictionary in n.draw.get_connections(): + var callable : Callable = cn["callable"] + if !callable.is_valid(): + n.draw.disconnect(callable) + x.remove_child(y) + p.add_child(y) + if p.get_child_count() > 1: + p.move_child(y, 0) + x.queue_free() + diff --git a/addons/script_splitter/core/contex/ssp_window.gd.uid b/addons/script_splitter/core/contex/ssp_window.gd.uid new file mode 100644 index 00000000..c5f4f988 --- /dev/null +++ b/addons/script_splitter/core/contex/ssp_window.gd.uid @@ -0,0 +1 @@ +uid://cdn4c7qori2ry diff --git a/addons/script_splitter/core/contex/window.gd b/addons/script_splitter/core/contex/window.gd new file mode 100644 index 00000000..3a78f54b --- /dev/null +++ b/addons/script_splitter/core/contex/window.gd @@ -0,0 +1,34 @@ +@tool +extends EditorContextMenuPlugin +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +var CONTEXT : String = "CUSTOM" +var ICON : Texture = null +var SHORTCUT : Shortcut = null +var CALLABLE : Callable +var VALIDATOR : Callable + +func _init(context : String, handle : Callable, validator : Callable, icon : Texture, input_key : Array[InputEvent] = []): + CONTEXT = context + CALLABLE = handle + ICON = icon + VALIDATOR = validator + if input_key.size() > 0: + SHORTCUT = Shortcut.new() + SHORTCUT.events = input_key + add_menu_shortcut(SHORTCUT, handle) + +func _popup_menu(paths : Variant) -> void: + if VALIDATOR.is_valid(): + if !VALIDATOR.call(paths): + return + if SHORTCUT: + add_context_menu_item_from_shortcut(CONTEXT, SHORTCUT, ICON) + else: + if CALLABLE.is_valid(): + add_context_menu_item(CONTEXT, CALLABLE, ICON) diff --git a/addons/script_splitter/core/contex/window.gd.uid b/addons/script_splitter/core/contex/window.gd.uid new file mode 100644 index 00000000..47bcb74c --- /dev/null +++ b/addons/script_splitter/core/contex/window.gd.uid @@ -0,0 +1 @@ +uid://cblflkellpdqg diff --git a/addons/script_splitter/core/editor/app.gd b/addons/script_splitter/core/editor/app.gd new file mode 100644 index 00000000..14eaf1a4 --- /dev/null +++ b/addons/script_splitter/core/editor/app.gd @@ -0,0 +1,23 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const MickeyTool = preload("./../../core/editor/tools/magic/mickey_tool.gd") +const ToolDB = preload("./../../core/editor/database/tool_db.gd") +const Manager = preload("./../../core/editor/godot/manager.gd") + +var _tool_db : ToolDB = null +var _manager : Manager = null + +func _init(manager : Manager, tool_db : ToolDB) -> void: + _manager = manager + _tool_db = tool_db + +func execute(_value : Variant = null) -> bool: + return false diff --git a/addons/script_splitter/core/editor/app.gd.uid b/addons/script_splitter/core/editor/app.gd.uid new file mode 100644 index 00000000..198c806a --- /dev/null +++ b/addons/script_splitter/core/editor/app.gd.uid @@ -0,0 +1 @@ +uid://b5denwbu6twf4 diff --git a/addons/script_splitter/core/editor/application/create_tool.gd b/addons/script_splitter/core/editor/application/create_tool.gd new file mode 100644 index 00000000..5ef807fe --- /dev/null +++ b/addons/script_splitter/core/editor/application/create_tool.gd @@ -0,0 +1,104 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const EditorTool = preload("./../../../core/editor/tools/editor_tool.gd") + +const HelperEditorTool = preload("./../../../core/editor/tools/helper_editor_tool.gd") +const ScriptEditorTool = preload("./../../../core/editor/tools/script_editor_tool.gd") +const TextEditorTool = preload("./../../../core/editor/tools/text_editor_tool.gd") + +var _tools : Array[EditorTool] = [ + ScriptEditorTool.new(), + HelperEditorTool.new(), + TextEditorTool.new() +] + +func execute(value : Variant = null) -> bool: + if !is_instance_valid(value) or !(value is Control): + return true + + var control : Control = value + + if !control.is_node_ready() or !control.is_inside_tree(): + return false + + for x : MickeyTool in _tool_db.get_tools(): + if x.has(control): + x.set_queue_free(false) + return true + + var index : int = control.get_index() + if !_manager.is_valid_item_index(index): + return false + + var root : Node = _get_root() + + if is_instance_valid(root): + var mt : MickeyTool = _tools[0].build(control) + var is_editor : bool = _is_editor(mt, control) + + if !is_editor: + for z : int in range(1, _tools.size(), 1): + var x : EditorTool = _tools[z] + mt = x.build(control) + + if mt != null: + break + + if mt != null: + mt.focus.connect(_manager.focus_tool) + mt.new_symbol.connect(_manager.set_symbol) + mt.clear.connect(_manager.clear_editors) + mt.ochorus(root) + + _tool_db.append(mt) + + _manager.tool_created() + _manager.update_metadata(mt) + + mt.trigger_focus() + return false + + if is_editor: + return true + + printerr("Error!, Can not build control for ", control.name) + return false + +func _is_editor(mt : MickeyTool, control : Control) -> bool: + if is_instance_valid(mt): + return true + + if control is ScriptEditorBase: + var sce : ScriptEditor = EditorInterface.get_script_editor() + if sce and control in sce.get_open_script_editors(): + if control.name.begins_with("@"): + if !("Script" in control.name): + return false + return true + return _manager.get_editor_list().get_item_tooltip(control.get_index()).is_empty() + + return false + +func _get_root() -> Node: + var root : Node = _manager.get_current_root() + if !is_instance_valid(root): + var splitters : Array[Node] = _manager.get_base_container().get_all_splitters() + if splitters.size() == 0: + for x : MickeyTool in _tool_db.get_tools(): + x.reset() + _manager.get_base_container().initialize_editor_container() + root = _manager.get_current_root() + else: + for x : Node in splitters: + if is_instance_valid(x) and !x.is_queued_for_deletion(): + root = x + break + return root diff --git a/addons/script_splitter/core/editor/application/create_tool.gd.uid b/addons/script_splitter/core/editor/application/create_tool.gd.uid new file mode 100644 index 00000000..d8754932 --- /dev/null +++ b/addons/script_splitter/core/editor/application/create_tool.gd.uid @@ -0,0 +1 @@ +uid://bsrituhgnfbm diff --git a/addons/script_splitter/core/editor/application/custom_split.gd b/addons/script_splitter/core/editor/application/custom_split.gd new file mode 100644 index 00000000..eb44c110 --- /dev/null +++ b/addons/script_splitter/core/editor/application/custom_split.gd @@ -0,0 +1,162 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const BaseContainer = preload("./../../../core/base/container.gd") +const SpliterItem = preload("./../../../core/ui/multi_split_container/split_container_item.gd") + +func execute(value : Variant = null) -> bool: + if value is Array: + if value.size() == 3: + if value[0] is Container and value[1] is int and value[2] is StringName: + var from : Container = value[0] + var index : int = value[1] + var type : StringName = value[2] + + if from is BaseContainer.SplitterContainer.SplitterEditorContainer.Editor: + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + + if x.get_root() == from and x.get_control().get_index() == index: + if type == &"LEFT": + + var c : Node = _manager.get_base_container().get_container_item(x.get_root()) + var cindex : int = 0 + + if !c: + return false + cindex = c.get_index() + + _manager.split_column.execute(x) + + if !c.is_node_ready(): + await c.ready + + c = _manager.get_base_container().get_container_item(x.get_root()) + + if is_instance_valid(c): + if cindex > -1 and cindex < c.get_parent().get_child_count() and c.get_index() != cindex: + c.get_parent().move_child.call_deferred(c, cindex) + + elif type == &"RIGHT": + var c : Node = _manager.get_base_container().get_container_item(x.get_root()) + var cindex : int = 0 + + if !c: + return false + + cindex = c.get_index() + 1 + + _manager.split_column.execute(x) + + if !c.is_node_ready(): + await c.ready + + c = _manager.get_base_container().get_container_item(x.get_root()) + + if is_instance_valid(c): + if cindex > -1 and cindex < c.get_parent().get_child_count() and c.get_index() != cindex: + c.get_parent().move_child.call_deferred(c, cindex) + + elif type == &"TOP": + var c : Node = _manager.get_base_container().get_container(x.get_root()) + var cindex : int = 0 + if !c: + return false + + var root : Node = c.get_parent() + + if !root: + return false + + cindex = root.get_index() + + _manager.split_row.execute(x) + + if !c.is_node_ready(): + await c.ready + + c = _manager.get_base_container().get_container_item(x.get_root()) + + if is_instance_valid(c): + var row : Node = c + for __ : int in range(0, 2, 1): + row = c.get_parent() + + if !is_instance_valid(row): + break + + if is_instance_valid(row): + var has : bool = false + for ___ : int in range(0, 3, 1): + if has: + break + for __ : int in range(0, 3, 1): + await Engine.get_main_loop().process_frame + if is_instance_valid(row) and is_instance_valid(c): + var _root : Node = c.get_parent() + if row.has_node(_root.get_path()) : + has = true + break + + if has and c and cindex > -1: + for __ : int in range(0, 2, 1): + c = c.get_parent() + if !c: + return false + root = c.get_parent() + if root and cindex < root.get_child_count() and c.get_index() != cindex: + root.move_child(c, cindex) + return true + + elif type == &"BOTTOM": + _manager.split_row.execute(x) + + var c : Node = _manager.get_base_container().get_container(x.get_root()) + var cindex : int = 0 + + if !c: + return false + + if !c.is_node_ready(): + await c.ready + + cindex = c.get_index() + 1 + + if c.get_index() < c.get_parent().get_child_count() - 1: + if is_instance_valid(c): + var row : Node = c + for __ : int in range(0, 2, 1): + row = c.get_parent() + if !is_instance_valid(row): + break + + if is_instance_valid(row): + var z : int = c.get_index() + if z > 0: + var has : bool = false + for ___ : int in range(0, 3, 1): + if has: + break + for __ : int in range(0, 3, 1): + await Engine.get_main_loop().process_frame + if is_instance_valid(row) and is_instance_valid(c): + var _root : Node = c.get_parent() + if row.has_node(_root.get_path()) and _root is SpliterItem: + has = true + break + if has and c and cindex > -1: + for __ : int in range(0, 2, 1): + c = c.get_parent() + if !c: + return false + var root : Node = c.get_parent() + if root and cindex < root.get_child_count() and c.get_index() != cindex: + root.move_child(c, cindex) + return true + return false diff --git a/addons/script_splitter/core/editor/application/custom_split.gd.uid b/addons/script_splitter/core/editor/application/custom_split.gd.uid new file mode 100644 index 00000000..9ae6dff4 --- /dev/null +++ b/addons/script_splitter/core/editor/application/custom_split.gd.uid @@ -0,0 +1 @@ +uid://dgqwhcax1guja diff --git a/addons/script_splitter/core/editor/application/focus_by_tab.gd b/addons/script_splitter/core/editor/application/focus_by_tab.gd new file mode 100644 index 00000000..bb6c230f --- /dev/null +++ b/addons/script_splitter/core/editor/application/focus_by_tab.gd @@ -0,0 +1,24 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +#Override app function. +func execute(value : Variant = null) -> bool: + if value is Array: + if value.size() > 1: + if value[0] is TabContainer and value[1] is int: + var control : TabContainer = value[0] + var index : int = value[1] + for x : MickeyTool in _tool_db.get_tools(): + if is_instance_valid(x): + if x.get_root() == control: + if x.get_control().get_index() == index: + x.trigger_focus() + return true + return false diff --git a/addons/script_splitter/core/editor/application/focus_by_tab.gd.uid b/addons/script_splitter/core/editor/application/focus_by_tab.gd.uid new file mode 100644 index 00000000..2ee4c319 --- /dev/null +++ b/addons/script_splitter/core/editor/application/focus_by_tab.gd.uid @@ -0,0 +1 @@ +uid://bipvmrq4th30m diff --git a/addons/script_splitter/core/editor/application/focus_tool.gd b/addons/script_splitter/core/editor/application/focus_tool.gd new file mode 100644 index 00000000..dcb8124a --- /dev/null +++ b/addons/script_splitter/core/editor/application/focus_tool.gd @@ -0,0 +1,114 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const BaseList = preload("./../../../core/base/list.gd") + +var unfocus_enabled : bool = true +var unfocus_color : Color = Color.DARK_GRAY + +func _init(manager : Manager, tool_db : ToolDB) -> void: + super(manager, tool_db) + _setup() + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func _on_change() -> void: + var dt : Array = ["plugin/script_splitter/editor/out_focus_color_enabled","plugin/script_splitter/editor/out_focus_color_value"] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + var current : Node = _manager.get_base_container().get_current_container() + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + var root : Control = x.get_root() + if root.modulate != Color.WHITE: + if unfocus_enabled: + root.modulate = unfocus_color + else: + root.modulate = Color.WHITE + elif unfocus_enabled: + if is_instance_valid(current): + if x.get_root() != current: + root.modulate = unfocus_color + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["unfocus_enabled", "plugin/script_splitter/editor/out_focus_color_enabled"] + ,["unfocus_color", "plugin/script_splitter/editor/out_focus_color_value"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + +func execute(value : Variant = null) -> bool: + if value is ScriptEditorBase: + var control : Control = value.get_base_editor() + for x : MickeyTool in _tool_db.get_tools(): + if x.has(control): + value = x + break + if value is MickeyTool: + var index : int = value.get_index() + var editor_list : BaseList = _manager.get_editor_list() + if editor_list.item_count() > index and index > -1: + var control : Node = value.get_control() + var root : Node = value.get_root() + if root is TabContainer: + var base : Manager.BaseContainer = _manager.get_base_container() + var _index : int = control.get_index() + if root.current_tab != _index and _index > -1 and _index < root.get_tab_count(): + if root.has_method(&"set_tab"): + root.call(&"set_tab", _index) + else: + root.set(&"current_tab", _index) + + var container : Control = base.get_current_container() + if is_instance_valid(container) and unfocus_enabled: + container.modulate = unfocus_color + + base.set_current_container(root) + + if is_instance_valid(root): + root.modulate = Color.WHITE + + var new_container : Node = base.get_container(root) + if is_instance_valid(new_container) and new_container.has_method(&"expand_splited_container"): + new_container.call(&"expand_splited_container", base.get_container_item(root)) + + if is_instance_valid(container): + container = base.get_container(container) + if is_instance_valid(container) and container != new_container and container.has_method(&"expand_splited_container"): + container.call(&"expand_splited_container", null) + + var grant_conainer : Node = base.get_editor_root_container(new_container) + if is_instance_valid(grant_conainer): + var parent : Node = grant_conainer.get_parent() + if is_instance_valid(parent) and parent.has_method(&"expand_splited_container"): + parent.call(&"expand_splited_container", base.get_editor_root_container(new_container)) + + if !editor_list.is_selected(index): + editor_list.select(index) + + _manager.io.update() + _manager.get_editor_list().updated.emit() + return false diff --git a/addons/script_splitter/core/editor/application/focus_tool.gd.uid b/addons/script_splitter/core/editor/application/focus_tool.gd.uid new file mode 100644 index 00000000..7a564f92 --- /dev/null +++ b/addons/script_splitter/core/editor/application/focus_tool.gd.uid @@ -0,0 +1 @@ +uid://bk4jykdijx7sj diff --git a/addons/script_splitter/core/editor/application/io.gd b/addons/script_splitter/core/editor/application/io.gd new file mode 100644 index 00000000..00175342 --- /dev/null +++ b/addons/script_splitter/core/editor/application/io.gd @@ -0,0 +1,256 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const BaseList = preload("./../../../core/base/list.gd") +const EDITOR = preload("./../../../core/ui/window/editor.tscn") + +var expanded : bool = false +var _updating : bool = false + +func update() -> void: + if _updating: + return + _updating = true + _update.call_deferred() + +func _update() -> void: + var base : Manager.BaseContainer = _manager.get_base_container() + var container : Node = base.get_current_container() + if is_instance_valid(container): + if expanded: + var cb : Node = base.get_container_item(container) + var ct : Array[Node] = container.get_tree().get_nodes_in_group(&"__SP_BR__") + for x : Node in container.get_tree().get_nodes_in_group(&"__SP_IC__"): + var v : bool = cb == x + for y : Node in x.get_children(): + if y is Control: + y.visible = v + + for __ : int in range(0, 2, 1): + for x : Node in ct: + if x is Control: + var v : bool = false + for y : Node in x.get_children(): + if y is Control and y.visible: + v = true + break + x.visible = v + + var can_split : bool = _can_split(container) + var can_merge_column : bool = _can_merge_column(base) + var can_merge_row : bool = _can_merge_row(base) + var can_sub_split : int = _sub() + var can_make_float : bool = (container.get_parent() is VBoxContainer) + + for x : Node in (Engine.get_main_loop()).get_nodes_in_group(&"__script_splitter__IO__"): + x.enable(&"SPLIT_COLUMN",can_split) + x.enable(&"MERGE_COLUMN",can_merge_column) + x.enable(&"SPLIT_ROW",can_split) + x.enable(&"MERGE_ROW",can_merge_row) + x.enable(&"SPLIT_SUB", can_sub_split == 0) + x.enable(&"MERGE_SPLIT_SUB", can_sub_split == 1) + x.enable(&"MAKE_FLOATING", can_make_float) + + _updating = false + +func _can_split(container : Node) -> bool: + return container != null and container.get_child_count() > 1 + +func _can_merge_column(base : Manager.BaseContainer) -> bool: + return base != null and base.get_current_splitters().size() > 1 + +func _can_merge_row(base : Manager.BaseContainer) -> bool: + return base != null and base.get_all_containers().size() > 1 + +func _sub() -> int: + var sc : ScriptEditor = EditorInterface.get_script_editor() + + if !is_instance_valid(sc.get_current_script()): + return -1 + + var ed : ScriptEditorBase = sc.get_current_editor() + var be : Control = ed.get_base_editor() + + + if be is CodeEdit: + if be.get_parent() is VSplitContainer: + return 1 + return 0 + + return -1 + +func _on_pin(btn : Button) -> void: + var st : String = btn.get_meta(&"I") + if st.is_empty(): + btn.queue_free() + return + + var bl : Manager.BaseList = _manager.get_editor_list() + for x : int in bl.item_count(): + if st == bl.get_item_tooltip(x): + bl.select(x) + return + +func _make_pin(tree : SceneTree, fn : String, tp : String, icn : Texture2D, mod : Color) -> void: + if mod == Color.BLACK: + mod = Color.WHITE + for x : Node in tree.get_nodes_in_group(&"__SP_PIN_ROOT__"): + var btn : Button = Button.new() + btn.text = fn + btn.icon = icn + btn.set_meta(&"I", tp) + btn.pressed.connect(_on_pin.bind(btn)) + btn.add_to_group(&"__SP_B_PIN__") + btn.set(&"theme_override_colors/icon_normal_color", mod) + btn.set(&"theme_override_colors/icon_focus_color", mod) + btn.set(&"theme_override_colors/icon_pressed_color", mod) + btn.set(&"theme_override_colors/icon_hover_color", mod) + btn.set(&"theme_override_colors/icon_hover_pressed_color", mod) + btn.set(&"theme_override_colors/icon_disabled_color", mod) + btn.set(&"theme_override_font_sizes/font_size", 12.0) + x.add_child(btn) + +func _remove_pin(tree : SceneTree, tp : String) -> bool: + for x : Node in tree.get_nodes_in_group(&"__SP_PIN_ROOT__"): + if x.has_meta(&"I"): + if x.get_meta(&"I") == tp: + x.queue_free() + return true + return false + +func execute(value : Variant = null) -> bool: + if value == null: + update() + return true + + if value is StringName: + if value.is_empty(): + update() + return true + + var base : Manager.BaseContainer = _manager.get_base_container() + var container : Node = base.get_current_container() + + var id : StringName = value + + match id: + &"EXPAND": + if is_instance_valid(container): + var ct : Array[Node] = container.get_tree().get_nodes_in_group(&"__SP_BR__") + if expanded: + for x : Node in container.get_tree().get_nodes_in_group(&"__SP_IC__"): + for y : Node in x.get_children(): + if y is Control: + y.visible = true + for x : Node in ct: + if x is Control: + x.visible = true + else: + var cb : Node = base.get_container_item(container) + for x : Node in container.get_tree().get_nodes_in_group(&"__SP_IC__"): + var v : bool = cb == x + for y : Node in x.get_children(): + if y is Control: + y.visible = v + + for __ : int in range(0, 2, 1): + for x : Node in ct: + if x is Control: + var v : bool = false + for y : Node in x.get_children(): + if y is Control and y.visible: + v = true + break + x.visible = v + + expanded = !expanded + + for x : Node in container.get_tree().get_nodes_in_group(&"__script_splitter__IO__"): + if x.has_method(&"get_button"): + var button : Button = x.call(&"get_button", id) + if is_instance_valid(button): + if expanded: + button.modulate = Color.GREEN + else: + button.modulate = Color.WHITE + + return true + &"PIN": + for x : MickeyTool in _tool_db.get_tools(): + if x.get_root() == container: + if container is TabContainer: + if container.current_tab == x.get_control().get_index(): + var list : Manager.BaseList = _manager.get_editor_list() + var idx : int = x.get_index() + + if list.item_count() > idx and idx > -1: + var nm : String = list.get_item_text(idx) + var ps : String = list.get_item_tooltip(idx) + + if _remove_pin(container.get_tree(), ps): + return true + + _make_pin(container.get_tree(), nm, ps, list.get_item_icon(idx), list.get_item_icon_modulate(idx)) + &"SPLIT_COLUMN": + if _can_split(container): + _manager.split_column.execute() + &"SPLIT_ROW": + if _can_split(container): + _manager.split_row.execute() + &"MERGE_COLUMN": + if _can_merge_column(base): + _manager.merge_tool.execute([null, false]) + &"MERGE_ROW": + if _can_merge_row(base): + _manager.merge_tool.execute([null, true]) + &"SPLIT_SUB": + if _sub() == 0: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + x.script_split() + break + &"MERGE_SPLIT_SUB": + if _sub() == 1: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + x.script_merge() + break + &"MAKE_FLOATING": + if (container.get_parent() is VBoxContainer): + for x : ToolDB.MickeyTool in _tool_db.get_tools(): + if x.has(container): + var y : Node = (_manager._base_container._editor_container.get_parent()) + var new_window : Window = EDITOR.instantiate() + y.add_child(new_window) + + var root : Node = new_window.call(&"get_root") + root.initialize(null, _manager.get_base_container()) + root.initialize_editor_contianer() + + var _root : Node = x.get_root() + + x.ochorus(root.call(&"get_current_editor")) + + if _root.get_child_count() < 1: + var item : Node = _manager.get_base_container().get_container_item(_root) + if item.get_child_count() == 1: + var cont : Node = _manager.get_base_container().get_container(_root) + if cont.get_child_count() == 1: + cont.queue_free() + else: + item.queue_free() + else: + if _root.get_parent() is VBoxContainer: + _root.get_parent().queue_free() + else: + _root.queue_free() + + new_window.setup() + new_window.update() + _manager.update() + return false + return false diff --git a/addons/script_splitter/core/editor/application/io.gd.uid b/addons/script_splitter/core/editor/application/io.gd.uid new file mode 100644 index 00000000..ae5b166f --- /dev/null +++ b/addons/script_splitter/core/editor/application/io.gd.uid @@ -0,0 +1 @@ +uid://dqgve5bbg0w1m diff --git a/addons/script_splitter/core/editor/application/merge_tool.gd b/addons/script_splitter/core/editor/application/merge_tool.gd new file mode 100644 index 00000000..bf953496 --- /dev/null +++ b/addons/script_splitter/core/editor/application/merge_tool.gd @@ -0,0 +1,91 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _get_tool(value : Variant) -> MickeyTool: + var container : MickeyTool = null + + if value == null: + container = _tool_db.get_by_reference(_manager.get_base_container().get_current_container()) + elif value is Node: + container = _tool_db.get_by_reference(value) + elif value is Resource: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value.resource_path + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + container = _tool_db.get_tool_id(x) + break + elif value is String: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + container = _tool_db.get_tool_id(x) + break + + return container + +func execute(value : Variant = null) -> bool: + if value is Array: + var mk : MickeyTool = _get_tool(value[0]) + + if is_instance_valid(mk) and value[1] is bool: + if mk and mk.is_valid(): + var root : Node = mk.get_root() + var control : Node = root + if control.is_in_group(&"__SC_SPLITTER__"): + var cbase : Manager.BaseContainer = _manager.get_base_container() + if value[1]: + control = cbase.get_container(control) + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + var node : Control = x.get_root() + if control == cbase.get_container(node): + x.reset() + control.queue_free() + else: + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + var node : Control = x.get_root() + if node: + if node == control: + x.reset() + else: + x.reset() + + var base : Manager.BaseContainer = _manager.get_base_container() + + if root == base.get_current_container(): + var nodes : Array[Node] = control.get_tree().get_nodes_in_group(&"__SC_SPLITTER__") + var container : Node = base.get_container_item(root) + + for n : Node in nodes: + if n == root: + continue + var _container : Node = base.get_container_item(n) + if _container.get_parent() == container.get_parent(): + var i0 : int = _container.get_index() + var i1 : int = container.get_index() + if i0 == i1 - 1 or i0 == i1 + 1: + base.set_current_container(n) + return true + + var z : int = nodes.find(root) + if z != -1: + if z == 0: + if nodes.size() > 1: + base.set_current_container(nodes[1]) + else: + if nodes.size() > 1: + base.set_current_container(nodes[z - 1]) + return true + #if control.get_child_count() == 0 or root.get_child_count() == 0: + #control.queue_free() + return false diff --git a/addons/script_splitter/core/editor/application/merge_tool.gd.uid b/addons/script_splitter/core/editor/application/merge_tool.gd.uid new file mode 100644 index 00000000..35ca41ec --- /dev/null +++ b/addons/script_splitter/core/editor/application/merge_tool.gd.uid @@ -0,0 +1 @@ +uid://cf43swgi3ydv8 diff --git a/addons/script_splitter/core/editor/application/refresh_warnings.gd b/addons/script_splitter/core/editor/application/refresh_warnings.gd new file mode 100644 index 00000000..dc7c307f --- /dev/null +++ b/addons/script_splitter/core/editor/application/refresh_warnings.gd @@ -0,0 +1,77 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +var _refreshing : bool = true + +func _init(manager : Manager, tool_db : ToolDB) -> void: + super(manager, tool_db) + _setup() + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func _on_change() -> void: + var dt : Array = ["plugin/script_splitter/behaviour/refresh_warnings_on_save"] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["_refreshing", "plugin/script_splitter/behaviour/refresh_warnings_on_save"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + +func execute(_value : Variant = null) -> bool: + if !_refreshing: + return true + + var sp : Array[Node] = Engine.get_main_loop().get_nodes_in_group(&"__SC_SPLITTER__") + var current : Control = _manager.get_base_container().get_current_container() + + var ctool : MickeyTool = null + var ltool : MickeyTool = null + + if sp.size() < 2: + return true + + for x : Variant in _tool_db.get_tools(): + if is_instance_valid(x): + if x.is_valid(): + var i : int = sp.find(x.get_root()) + var container : Node = sp[i] + if container is TabContainer: + var indx : int = x.get_control().get_index() + if container.current_tab == indx: + if container == current: + ctool = x + ltool = x + _manager.select_editor_by_index(x.get_index()) + + + if is_instance_valid(ctool) and ctool != ltool: + _manager.select_editor_by_index(ctool.get_index()) + + return true diff --git a/addons/script_splitter/core/editor/application/refresh_warnings.gd.uid b/addons/script_splitter/core/editor/application/refresh_warnings.gd.uid new file mode 100644 index 00000000..b23bd463 --- /dev/null +++ b/addons/script_splitter/core/editor/application/refresh_warnings.gd.uid @@ -0,0 +1 @@ +uid://c0wasvo7fwcqr diff --git a/addons/script_splitter/core/editor/application/remove_by_tab.gd b/addons/script_splitter/core/editor/application/remove_by_tab.gd new file mode 100644 index 00000000..e2a98726 --- /dev/null +++ b/addons/script_splitter/core/editor/application/remove_by_tab.gd @@ -0,0 +1,26 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func execute(value : Variant = null) -> bool: + if value is Array: + var control : Control = value[0] + var index : int = value[1] + + if index < 0: + return false + + for x : MickeyTool in _tool_db.get_tools(): + if x.get_root() == control and x.get_control().get_index() == index: + var _index : int = x.get_index() + x.reset() + _manager.get_editor_list().remove(_index) + return true + return false + diff --git a/addons/script_splitter/core/editor/application/remove_by_tab.gd.uid b/addons/script_splitter/core/editor/application/remove_by_tab.gd.uid new file mode 100644 index 00000000..631d7822 --- /dev/null +++ b/addons/script_splitter/core/editor/application/remove_by_tab.gd.uid @@ -0,0 +1 @@ +uid://bht1hix6hophq diff --git a/addons/script_splitter/core/editor/application/reparent_tool.gd b/addons/script_splitter/core/editor/application/reparent_tool.gd new file mode 100644 index 00000000..313ffc08 --- /dev/null +++ b/addons/script_splitter/core/editor/application/reparent_tool.gd @@ -0,0 +1,21 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func execute(value : Variant = null) -> bool: + if value is Array: + if value[0] is Control and value[1] is int: + if value[1] < 0: + return false + for x : MickeyTool in _tool_db.get_tools(): + if x.get_index() == value[1]: + if x.is_valid(): + x.ochorus(value[0]) + return true + return false diff --git a/addons/script_splitter/core/editor/application/reparent_tool.gd.uid b/addons/script_splitter/core/editor/application/reparent_tool.gd.uid new file mode 100644 index 00000000..1c1e1cf9 --- /dev/null +++ b/addons/script_splitter/core/editor/application/reparent_tool.gd.uid @@ -0,0 +1 @@ +uid://ckvujn0hnsm11 diff --git a/addons/script_splitter/core/editor/application/rmb_menu.gd b/addons/script_splitter/core/editor/application/rmb_menu.gd new file mode 100644 index 00000000..2b22387c --- /dev/null +++ b/addons/script_splitter/core/editor/application/rmb_menu.gd @@ -0,0 +1,30 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func execute(value : Variant = null) -> bool: + if value is Array: + var idx : int = value[0] + var node : Node = value[1] + + if idx < 0: + return false + + for x : MickeyTool in _tool_db.get_tools(): + if x.get_root() == node: + if x.get_control().get_index() == idx: + var list : Manager.BaseList = _manager.get_editor_list() + var indx : int = x.get_index() + if list.item_count() > indx and indx > -1: + var el : ItemList = list.get_editor_list() + el.item_clicked.emit(indx,el.get_local_mouse_position(), MOUSE_BUTTON_RIGHT) + + return true + + return false diff --git a/addons/script_splitter/core/editor/application/rmb_menu.gd.uid b/addons/script_splitter/core/editor/application/rmb_menu.gd.uid new file mode 100644 index 00000000..2b6a7a5c --- /dev/null +++ b/addons/script_splitter/core/editor/application/rmb_menu.gd.uid @@ -0,0 +1 @@ +uid://dj5eoum4nippb diff --git a/addons/script_splitter/core/editor/application/select_by_index.gd b/addons/script_splitter/core/editor/application/select_by_index.gd new file mode 100644 index 00000000..99270de7 --- /dev/null +++ b/addons/script_splitter/core/editor/application/select_by_index.gd @@ -0,0 +1,30 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func execute(value : Variant = null) -> bool: + if value is int: + if value < 0: + return false + + for x : MickeyTool in _tool_db.get_tools(): + if x.get_index() == value: + var root : Variant = x.get_root() + if is_instance_valid(root): + if root is TabContainer: + if !(root.get_window().has_focus()): + root.get_window().grab_focus() + var index : int = x.get_control().get_index() + if root.current_tab != index and index > -1 and root.get_tab_count() > index: + if root.has_method(&"set_tab"): + root.call(&"set_tab", index) + else: + root.current_tab = index + return true + return false diff --git a/addons/script_splitter/core/editor/application/select_by_index.gd.uid b/addons/script_splitter/core/editor/application/select_by_index.gd.uid new file mode 100644 index 00000000..240bcfab --- /dev/null +++ b/addons/script_splitter/core/editor/application/select_by_index.gd.uid @@ -0,0 +1 @@ +uid://bej35a842s2yd diff --git a/addons/script_splitter/core/editor/application/split_column.gd b/addons/script_splitter/core/editor/application/split_column.gd new file mode 100644 index 00000000..a72299c5 --- /dev/null +++ b/addons/script_splitter/core/editor/application/split_column.gd @@ -0,0 +1,46 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +func execute(value : Variant = null) -> bool: + var _tool : MickeyTool = null + if value == null: + value = _manager.get_base_container().get_current_container() + elif value is Resource: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value.resource_path + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + elif value is String: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + elif value is MickeyTool: + _tool = value + + if _tool == null: + if value is MickeyTool: + _tool = value + elif value is Node: + _tool = _tool_db.get_by_reference(value) + + if is_instance_valid(_tool) and _tool.is_valid(): + if _manager._focus_tool.unfocus_enabled: + _tool.get_root().modulate = _manager._focus_tool.unfocus_color + var idx : int = _tool.get_index() + if idx > -1 and _manager.get_editor_list().item_count() > idx: + _manager.move_tool(_manager.get_base_container().new_column(), idx) + _manager.io.update() + return true + + return false diff --git a/addons/script_splitter/core/editor/application/split_column.gd.uid b/addons/script_splitter/core/editor/application/split_column.gd.uid new file mode 100644 index 00000000..9e0a782b --- /dev/null +++ b/addons/script_splitter/core/editor/application/split_column.gd.uid @@ -0,0 +1 @@ +uid://bomyp1t030hd diff --git a/addons/script_splitter/core/editor/application/split_row.gd b/addons/script_splitter/core/editor/application/split_row.gd new file mode 100644 index 00000000..586ad7a6 --- /dev/null +++ b/addons/script_splitter/core/editor/application/split_row.gd @@ -0,0 +1,45 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +func execute(value : Variant = null) -> bool: + var _tool : MickeyTool = null + + if value == null: + value = _manager.get_base_container().get_current_container() + elif value is Resource: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value.resource_path + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + elif value is String: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + + if _tool == null: + if value is MickeyTool: + _tool = value + elif value is Node: + _tool = _tool_db.get_by_reference(value) + + if is_instance_valid(_tool) and _tool.is_valid(): + if _manager._focus_tool.unfocus_enabled: + _tool.get_root().modulate = _manager._focus_tool.unfocus_color + var idx : int = _tool.get_index() + if idx > -1 and _manager.get_editor_list().item_count() > idx: + _manager.move_tool(_manager.get_base_container().new_row(), idx) + _manager.io.update() + return true + + return false diff --git a/addons/script_splitter/core/editor/application/split_row.gd.uid b/addons/script_splitter/core/editor/application/split_row.gd.uid new file mode 100644 index 00000000..bf4ce13c --- /dev/null +++ b/addons/script_splitter/core/editor/application/split_row.gd.uid @@ -0,0 +1 @@ +uid://bg2573oxujrny diff --git a/addons/script_splitter/core/editor/application/swap_tab.gd b/addons/script_splitter/core/editor/application/swap_tab.gd new file mode 100644 index 00000000..eefaf047 --- /dev/null +++ b/addons/script_splitter/core/editor/application/swap_tab.gd @@ -0,0 +1,79 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const BaseContainer = preload("./../../../core/base/container.gd") + +var _last_tool : MickeyTool = null + +func execute(value : Variant = null) -> bool: + if value is Array: + if value.size() == 3: + if value[0] is Container and value[1] is int and value[2] is Container: + var from : Container = value[0] + var index : int = value[1] + var to : Container = value[2] + + if from == to: + return false + + if from is BaseContainer.SplitterContainer.SplitterEditorContainer.Editor and to is BaseContainer.SplitterContainer.SplitterEditorContainer.Editor: + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + if x.get_root() == from and x.get_control().get_index() == index: + if _last_tool == x: + return false + _last_tool = x + x.ochorus(to) + _manager.clear_editors() + set_deferred(&"_last_tool", null) + return true + else: + if value[0] is String and value[1] is String and value[2] is bool: + var base : Manager.BaseList = _manager.get_editor_list() + var from : String = value[0] + var left : bool = value[2] + var to : String = value[1] + var fm : MickeyTool = null + var tm : MickeyTool = null + if from == to: + return false + for x : MickeyTool in _tool_db.get_tools(): + if !x.is_valid(): + continue + var t : String = base.get_item_tooltip(x.get_index()) + if from == t: + fm = x + elif to == t: + tm = x + if is_instance_valid(fm) and is_instance_valid(tm) and fm != tm: + var froot : Node = fm.get_root() + var troot : Node = tm.get_root() + if froot == troot: + if left: + if froot is TabContainer: + _manager.move_item_container(froot, fm.get_index(), maxi(tm.get_index() - 1, 0)) + froot.move_child(fm.get_control(), maxi(tm.get_control().get_index() - 1,0)) + else: + if froot is TabContainer: + _manager.move_item_container(froot, fm.get_index(), tm.get_index()) + froot.move_child(fm.get_control(), tm.get_control().get_index()) + else: + if froot.get_child_count() == 1: + + if _manager.merge_tool.execute([tm.get_control(), froot.get_parent().get_child_count() == 1]): + fm.ochorus(troot) + #if froot.get_parent().get_child_count() == 1: + #froot.get_parent().queue_free() + #else: + #froot.queue_free() + #_manager.get_base_container().update_split_container() + else: + fm.ochorus(troot) + return true + return false diff --git a/addons/script_splitter/core/editor/application/swap_tab.gd.uid b/addons/script_splitter/core/editor/application/swap_tab.gd.uid new file mode 100644 index 00000000..ff85dd1c --- /dev/null +++ b/addons/script_splitter/core/editor/application/swap_tab.gd.uid @@ -0,0 +1 @@ +uid://cwgbj8fqlg6wm diff --git a/addons/script_splitter/core/editor/application/update_list_selection.gd b/addons/script_splitter/core/editor/application/update_list_selection.gd new file mode 100644 index 00000000..9e63d8b7 --- /dev/null +++ b/addons/script_splitter/core/editor/application/update_list_selection.gd @@ -0,0 +1,105 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +var LIST_VISIBLE_SELECTED_COLOR : Color = Color.from_string("7b68ee", Color.CORNFLOWER_BLUE) +var LIST_VISIBLE_OTHERS_COLOR : Color = Color.from_string("4835bb", Color.DARK_BLUE) + + +var _script_list_selection : bool = false + +func _init(manager : Manager, tool_db : ToolDB) -> void: + super(manager, tool_db) + _setup() + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func _on_change() -> void: + var dt : Array = [ + "plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/selected_color" + ,"plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/others_color" + ] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["LIST_VISIBLE_SELECTED_COLOR", "plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/selected_color"] + ,["LIST_VISIBLE_OTHERS_COLOR", "plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/others_color"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + + +func execute(value : Variant = null) -> bool: + if !value is Array or value.size() < 1: + return false + + if _script_list_selection: + return true + + _script_list_selection = true + + + var _editor_list : ItemList = value[0] + var _script_list : ItemList = value[1] + + var selected : String = "" + var others_selected : PackedStringArray = [] + + + var current : TabContainer = _manager.get_base_container().get_current_container() + + + for x : MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + var _root : Node = x.get_root() + if _root.current_tab == x.get_control().get_index(): + var idx : int = x.get_index() + if _editor_list.item_count > idx and idx > -1: + if _root == current: + selected = _editor_list.get_item_tooltip(idx) + else: + others_selected.append(_editor_list.get_item_tooltip(idx)) + + var color : Color = LIST_VISIBLE_SELECTED_COLOR + var color_ctn : Color = LIST_VISIBLE_SELECTED_COLOR + var others : Color = LIST_VISIBLE_OTHERS_COLOR + color.a = 0.5 + others.a = 0.5 + color_ctn.a = 0.25 + + for x : int in _script_list.item_count: + var mt : String = _script_list.get_item_tooltip(x) + if selected == mt: + _script_list.set_item_custom_bg_color(x, color) + _script_list.set_item_custom_fg_color(x, Color.WHITE) + _script_list.select(x, true) + elif others_selected.has(mt): + _script_list.set_item_custom_bg_color(x, others) + else: + _script_list.set_item_custom_bg_color(x, Color.TRANSPARENT) + _script_list.ensure_current_is_visible() + set_deferred(&"_script_list_selection", false) + return false diff --git a/addons/script_splitter/core/editor/application/update_list_selection.gd.uid b/addons/script_splitter/core/editor/application/update_list_selection.gd.uid new file mode 100644 index 00000000..6b1f8d03 --- /dev/null +++ b/addons/script_splitter/core/editor/application/update_list_selection.gd.uid @@ -0,0 +1 @@ +uid://d0fdvav3whi4t diff --git a/addons/script_splitter/core/editor/application/update_metadata.gd b/addons/script_splitter/core/editor/application/update_metadata.gd new file mode 100644 index 00000000..91244cef --- /dev/null +++ b/addons/script_splitter/core/editor/application/update_metadata.gd @@ -0,0 +1,80 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const BaseList = preload("./../../../core/base/list.gd") +var _buffer : Dictionary = {} + +func execute(value : Variant = null) -> bool: + var list : BaseList = _manager.get_editor_list() + if is_instance_valid(value) and value is MickeyTool: + _update(value, list) + else: + var arr : Array[MickeyTool] = _tool_db.get_tools() + for x : int in range(arr.size() - 1, -1, -1): + var _tool : Variant = arr[x] + if !is_instance_valid(_tool): + arr.remove_at(x) + continue + _update(_tool, list) + + var dict : Dictionary = {} + for x : ToolDB.MickeyTool in _tool_db.get_tools(): + if !x.is_valid(): + continue + var _root : Node = x.get_root_control() + if dict.has(_root): + continue + dict[_root] = true + if _root.has_method(&"update"): + _root.call_deferred(&"update") + return true + +func _update(mk : MickeyTool, list : BaseList) -> void: + if !is_instance_valid(mk) or !mk.is_valid(): + return + var index : int = mk.get_index() + if index > -1 and list.item_count() > index: + var icon : Texture2D = list.get_item_icon(index) + var modulate : Color = list.get_item_icon_modulate(index) + if icon and modulate != Color.WHITE and modulate != Color.BLACK: + var root : Node = mk.get_root() + var make : bool = true + if root.has_method(&"set_icon_color"): + make = root.call(&"set_icon_color", modulate) + if make: + if _buffer.has(icon): + icon = _buffer[icon] + else: + var new_icon : Texture2D = mod_image(icon, modulate) + _buffer[icon] = new_icon + icon = new_icon + + mk.update_metadata( + list.get_item_text(index), + list.get_item_tooltip(index), + icon + ) + +func mod_image(icon: Texture2D, modulate_color: Color) -> Texture2D: + var image : Image = icon.get_image() + if image.get_format() != Image.FORMAT_RGBA8: + image.convert(Image.FORMAT_RGBA8) + + var width : int = image.get_width() + var height : int = image.get_height() + + for x : int in range(width): + for y : int in range(height): + var original_color: Color = image.get_pixel(x, y) + var modulated_color: Color = modulate_color + if original_color.a > 0.0: + modulated_color.a = original_color.a + image.set_pixel(x, y, modulated_color) + + return ImageTexture.create_from_image(image) diff --git a/addons/script_splitter/core/editor/application/update_metadata.gd.uid b/addons/script_splitter/core/editor/application/update_metadata.gd.uid new file mode 100644 index 00000000..a51432cd --- /dev/null +++ b/addons/script_splitter/core/editor/application/update_metadata.gd.uid @@ -0,0 +1 @@ +uid://bk6hirh5yekc5 diff --git a/addons/script_splitter/core/editor/application/user_tab_close.gd b/addons/script_splitter/core/editor/application/user_tab_close.gd new file mode 100644 index 00000000..88f00550 --- /dev/null +++ b/addons/script_splitter/core/editor/application/user_tab_close.gd @@ -0,0 +1,63 @@ +@tool +extends "./../../../core/editor/app.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func execute(arr : Variant = null) -> bool: + var value : Variant = arr[0] + var type : int = arr[1] + + var _tool : MickeyTool = null + + if value == null: + value = _manager.get_base_container().get_current_container() + + elif value is Resource: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value.resource_path + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + elif value is String: + var list : ItemList = _manager.get_editor_list().get_editor_list() + var pth : String = value + for x : int in list.item_count: + if pth == list.get_item_tooltip(x): + _tool = _tool_db.get_tool_id(x) + break + + if _tool == null: + if value is MickeyTool: + _tool = value + elif value is Node: + _tool = _tool_db.get_by_reference(value) + + if is_instance_valid(_tool): + var root : Node = _tool.get_root() + var indx : int = _tool.get_control().get_index() + + var index : PackedInt32Array = [] + + for x : MickeyTool in _tool_db.get_tools(): + if x.get_root() == root: + if type < 0: + if x.get_control().get_index() < indx: + index.append(x.get_index()) + elif type > 0: + if x.get_control().get_index() > indx: + index.append(x.get_index()) + else: + if x.get_control().get_index() != indx: + index.append(x.get_index()) + + index.sort() + + for z : int in range(index.size() - 1, -1, -1): + _manager.get_editor_list().remove(index[z]) + return false diff --git a/addons/script_splitter/core/editor/application/user_tab_close.gd.uid b/addons/script_splitter/core/editor/application/user_tab_close.gd.uid new file mode 100644 index 00000000..d26c4578 --- /dev/null +++ b/addons/script_splitter/core/editor/application/user_tab_close.gd.uid @@ -0,0 +1 @@ +uid://c8c77dtgjpvxr diff --git a/addons/script_splitter/core/editor/coroutine/task.gd b/addons/script_splitter/core/editor/coroutine/task.gd new file mode 100644 index 00000000..d6a22c45 --- /dev/null +++ b/addons/script_splitter/core/editor/coroutine/task.gd @@ -0,0 +1,23 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +var _task : Array[Callable] = [] + +func has(callable : Callable) -> bool: + return _task.has(callable) + +func add(task : Callable) -> void: + if task.is_valid(): + _task.append(task) + +func update() -> void: + for task : Callable in _task: + if task.is_valid(): + task.call() + _task.clear() diff --git a/addons/script_splitter/core/editor/coroutine/task.gd.uid b/addons/script_splitter/core/editor/coroutine/task.gd.uid new file mode 100644 index 00000000..0f41e24a --- /dev/null +++ b/addons/script_splitter/core/editor/coroutine/task.gd.uid @@ -0,0 +1 @@ +uid://qbsuad1aohqy diff --git a/addons/script_splitter/core/editor/database/tool_db.gd b/addons/script_splitter/core/editor/database/tool_db.gd new file mode 100644 index 00000000..d79c88e2 --- /dev/null +++ b/addons/script_splitter/core/editor/database/tool_db.gd @@ -0,0 +1,64 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const MickeyTool = preload("./../../../core/editor/tools/magic/mickey_tool.gd") + +var _tools : Array[MickeyTool] = [] + +func get_tools() -> Array[MickeyTool]: + return _tools + +func append(mk : MickeyTool) -> void: + _tools.append(mk) + +func garbage(val : int) -> void: + if val == 1: + for x : Variant in _tools: + if is_instance_valid(x): + (x as MickeyTool).set_queue_free(true) + elif val == 0: + for x : int in range(_tools.size() - 1, -1, -1): + var variant : Variant = _tools[x] + if !is_instance_valid(variant): + _tools.remove_at(x) + + if !variant.is_valid(): + if !is_instance_valid(variant.get_owner()): + var root : Node = variant.get_root() + if is_instance_valid(root): + variant.get_root().queue_free() + variant.set_queue_free(true) + + if (variant as MickeyTool).is_queue_free(): + _tools.remove_at(x) + +func get_tool_id(id : int) -> MickeyTool: + for x : MickeyTool in _tools: + if x.get_index() == id: + return x + return null + +func has_tool_id(id : int) -> bool: + for x : MickeyTool in _tools: + if x.get_index() == id: + return true + return false + + +func clear() -> void: + for x : MickeyTool in _tools: + if is_instance_valid(x): + x.reset() + _tools.clear() + +func get_by_reference(control : Node) -> MickeyTool: + for x : MickeyTool in _tools: + if x.has(control): + return x + return null diff --git a/addons/script_splitter/core/editor/database/tool_db.gd.uid b/addons/script_splitter/core/editor/database/tool_db.gd.uid new file mode 100644 index 00000000..24e16c37 --- /dev/null +++ b/addons/script_splitter/core/editor/database/tool_db.gd.uid @@ -0,0 +1 @@ +uid://dv6xyd03fg7kj diff --git a/addons/script_splitter/core/editor/godot/manager.gd b/addons/script_splitter/core/editor/godot/manager.gd new file mode 100644 index 00000000..19706924 --- /dev/null +++ b/addons/script_splitter/core/editor/godot/manager.gd @@ -0,0 +1,404 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const CreateTool = preload("./../../../core/editor/application/create_tool.gd") +const UpdateMetadata = preload("./../../../core/editor/application/update_metadata.gd") +const FocusTool = preload("./../../../core/editor/application/focus_tool.gd") +const SelectByIndex = preload("./../../../core/editor/application/select_by_index.gd") +const FocusByTab = preload("./../../../core/editor/application/focus_by_tab.gd") +const ReparentTool = preload("./../../../core/editor/application/reparent_tool.gd") +const MergeTool = preload("./../../../core/editor/application/merge_tool.gd") +const SplitColumn = preload("./../../../core/editor/application/split_column.gd") +const SplitRow = preload("./../../../core/editor/application/split_row.gd") +const RemoveByTab = preload("./../../../core/editor/application/remove_by_tab.gd") +const RefreshWarnings = preload("./../../../core/editor/application/refresh_warnings.gd") +const UpdateListSelection = preload("./../../../core/editor/application/update_list_selection.gd") +const SwapTab = preload("./../../../core/editor/application/swap_tab.gd") +const RmbMenu = preload("./../../../core/editor/application/rmb_menu.gd") +const UserTabClose = preload("./../../../core/editor/application/user_tab_close.gd") +const Io = preload("./../../../core/editor/application/io.gd") +const CustomSplit = preload("./../../../core/editor/application/custom_split.gd") + +const ToolDB = preload("./../../../core/editor/database/tool_db.gd") +const Task = preload("./../../../core/editor/coroutine/task.gd") + +const BaseContainer = preload("./../../../core/base/container.gd") +const BaseList = preload("./../../../core/base/list.gd") + + +signal update_request() + +# API +var split_column : SplitColumn = null +var split_row : SplitRow = null +var refresh_warnings : RefreshWarnings = null +var merge_tool : MergeTool = null + +# APPLICATION +var _create_tool : CreateTool = null +var _focus_tool : FocusTool = null +var _update_metadata : UpdateMetadata = null +var _select_by_index : SelectByIndex = null +var _focus_by_tab : FocusByTab = null +var _remove_by_tab : RemoveByTab = null +var _reparent_tool : ReparentTool = null +var _update_list_selection : UpdateListSelection = null +var _rmb_menu : RmbMenu = null +var _user_tab_close : UserTabClose = null +var _custom_split : CustomSplit = null + +var io : Io = null +var swap_tab : SwapTab = null + +# DB +var _tool_db : ToolDB = null + +# REF +var _base_container : BaseContainer = null +var _base_list : BaseList = null +var _task : Task = null + +func _app_setup() -> void: + _task = Task.new() + _tool_db = ToolDB.new() + + _focus_tool = FocusTool.new(self, _tool_db) + _update_metadata = UpdateMetadata.new(self, _tool_db) + _create_tool = CreateTool.new(self, _tool_db) + _select_by_index = SelectByIndex.new(self, _tool_db) + _focus_by_tab = FocusByTab .new(self, _tool_db) + _reparent_tool = ReparentTool.new(self, _tool_db) + merge_tool = MergeTool.new(self, _tool_db) + _remove_by_tab = RemoveByTab.new(self, _tool_db) + _update_list_selection = UpdateListSelection.new(self, _tool_db) + swap_tab = SwapTab.new(self, _tool_db) + _rmb_menu = RmbMenu.new(self, _tool_db) + _user_tab_close = UserTabClose.new(self, _tool_db) + _custom_split = CustomSplit.new(self, _tool_db) + + io = Io.new(self, _tool_db) + + split_column = SplitColumn.new(self, _tool_db) + split_row = SplitRow.new(self, _tool_db) + refresh_warnings = RefreshWarnings.new(self, _tool_db) + + _base_list.update_selections_callback = _update_list_selection.execute + +func update_list(__ : Variant) -> void: + _base_list.update_list_selection() + +func get_current_tool(ref : Node = null) -> ToolDB.MickeyTool: + if ref == null: + ref = _base_container.get_current_container() + return _tool_db.get_by_reference(ref) + +func _init(base_container : BaseContainer, base_list : BaseList) -> void: + _base_container = base_container + _base_list = base_list + + #_base_list.set_handler(self) +# + _base_list.updated.connect(update_all_metadata) + _base_list.item_selected.connect(_on_item_selected) + _base_list.move_item.connect(_move_item_list) + _base_container.update.connect(update_metadata) + _base_container.focus_by_tab.connect(_on_focus_tab) + _base_container.remove_by_tab.connect(_on_remove_tab) + + _base_container.swap_tab.connect(_onswap_tab) + _base_container.same_swap_tab.connect(_on_same_swap_tab) + _base_container.change_container.connect(update_list) + + _base_container.rmb_click.connect(_on_tab_rmb) + + _base_container.exiting.connect(_on_exiting) + + _app_setup() + +func _on_exiting() -> void: + _tool_db.clear() + +func get_total_editors() -> int: + var container : Control = _base_container.get_current_container() + if is_instance_valid(container): + return container.get_child_count() + return 0 + +func get_current_totaL_editors(current : Node) -> int: + var container : Control = null + + if current == null: + container = _base_container.get_current_container() + elif current is Node: + container = _tool_db.get_by_reference(current).get_root() + + if is_instance_valid(container): + return container.get_child_count() + return 0 + +func get_total_split_container(by_row : bool) -> int: + if by_row: + var rows : Array = [] + for x : Node in _base_container.get_all_containers(): + var parent : Node = x.get_parent() + if parent: + if !rows.has(parent): + rows.append(parent) + return rows.size() + else: + return _base_container.get_all_containers().size() + +func get_total_splitters() -> int: + return _base_container.get_all_splitters().size() + +func get_current_total_splitters(current : Node) -> int: + if current is CodeEdit: + var container : Control = null + var value : ToolDB.MickeyTool = _tool_db.get_by_reference(current) + if is_instance_valid(value) and value.is_valid(): + container = _base_container.get_container(value.get_root()) + if is_instance_valid(container): + return container.get_child_count() + return 0 + return _base_container.get_current_splitters().size() + +func clear() -> void: + _tool_db.clear() + +func reset_by_control(control : Node) -> void: + var tls : Array[ToolDB.MickeyTool] = [] + for x : ToolDB.MickeyTool in _tool_db.get_tools(): + if x.is_valid(): + if control.find_child(x.get_control().name, true, false): + tls.append(x) + + for t : ToolDB.MickeyTool in tls: + t.reset() + +func reset() -> void: + _tool_db.clear() + + _base_container.reset() + _base_list.reset() + +func _onswap_tab(from : Container, index : int, to : Container) -> void: + swap_tab.execute([from, index, to]) + +func _on_same_swap_tab(from : Container, index : int, type : StringName) -> void: + _custom_split.execute([from, index, type]) + +func _on_focus_tab(tab : TabContainer, index : int) -> void: + _focus_by_tab.execute([tab, index]) + +func _on_remove_tab(tab : TabContainer, index : int) -> void: + _remove_by_tab.execute([tab, index]) + +func _on_item_selected(i : int) -> void: + _select_by_index.execute(i) + +func is_valid_item_index(index : int) -> bool: + return index > -1 and _base_list.item_count() > index and !_base_list.get_item_tooltip(index).is_empty() and !_base_list.get_item_text(index).is_empty() + +func update() -> bool: + if !_base_container.has_method(&"is_active") or !_base_container.is_active(): + return false + + _task.update() + _tool_db.garbage(1) + + var update_required : bool = false + for x : Node in _base_container.get_editors(): + update_required = !_create_tool.execute(x) || update_required + + _tool_db.garbage(0) + _base_container.garbage() + + _select_by_index.execute(_base_list.get_selected_id()) + + _update_root() + + _base_container.update_split_container() + _base_list.update_list() + return !update_required + +# API +func set_symbol(__ : String) -> void: + var tl : ToolDB.MickeyTool = _tool_db.get_tool_id(_base_list.get_selected_id()) + if is_instance_valid(tl): + _focus_tool.execute(tl) + var gui : Node = tl.get_gui() + if gui is CodeEdit: + _center.call_deferred(gui) + else: + for x : Node in gui.get_children(): + if x is RichTextLabel: + _center.call_deferred(x) + +func _center(gui : Variant) -> void: + if is_instance_valid(gui): + if gui is CodeEdit: + if gui.get_caret_count() > 0: + gui.scroll_vertical = gui.get_scroll_pos_for_line(maxi(gui.get_caret_line(0) - 1, 0)) + gui.center_viewport_to_caret.call_deferred(0) + +func unsplit_column(current : Variant) -> void: + if merge_tool.execute([current, false]): + update_request.emit() + +func unsplit_row(current : Variant) -> void: + if merge_tool.execute([current, true]): + update_request.emit() + +func move_tool(control : Control, index : int) -> bool: + return _reparent_tool.execute([control, index]) + +func get_current_root() -> Control: + return _base_container.get_current_editor() + +func get_editor_list() -> BaseList: + return _base_list + +func get_base_container() -> BaseContainer: + return _base_container + +func get_editor_container() -> TabContainer: + return _base_container.get_editor_container() + +func select_editor_by_index(index : int) -> void: + _base_list.select(index) + +func focus_tool(mk : Variant) -> void: + _focus_tool.execute(mk) + +func tool_created() -> void: + _base_container.tool_created() + +func update_metadata(mk : Variant = null) -> void: + _task.add(_update_metadata.execute.bind(mk)) + update_request.emit() + +func update_all_metadata() -> void: + if !_task.has(_update_metadata.execute): + _task.add(_update_metadata.execute) + update_request.emit() + +func clear_editors() -> void: + if !_task.has(_clear_editor): + _task.add(_clear_editor) + update_request.emit() + +func _clear_editor() -> void: + var spls : Array[Node] = _base_container.get_all_splitters() + var total : int = spls.size() + + if total > 1: + total = 0 + for x : Node in spls: + if is_instance_valid(x): + if x.is_queued_for_deletion(): + continue + total += 1 + + if total > 1: + for x : Node in spls: + if x.get_child_count() == 0: + if total < 2: + return + + var c : Node = _base_container.get_container_item(x) + if c and !c.is_queued_for_deletion(): + var container : Node = _base_container.get_container(x) + if container and container.get_child_count() < 2: + container.get_parent().queue_free() + c.queue_free() + total -= 1 + +func _update_root() -> void: + var root : Control = _base_container.get_root_container() + + if root: + var v : bool = false + var nodes : Array[Node] = root.get_tree().get_nodes_in_group(&"__SP_IC__") + var total : int = nodes.size() + for x : Node in nodes: + if total < 2: + break + if x.get_child_count() == 0: + x.queue_free() + total -= 1 + else: + if x.get_child(0).get_child_count() == 0: + x.queue_free() + total -= 1 + + for x : Node in _base_container.get_all_splitters(): + if x.get_child_count() > 0: + v = true + break + root.get_parent().visible = v + +func get_control_tool_by_current(current : Variant) -> Node: + var root : Node = null + if null == current or current is PackedStringArray and current.size() == 0: + current = get_base_container().get_current_container() + if current is TabContainer: + var i : int = current.current_tab + if i > -1: + current = current.get_child(i) + if current: + if current is String: + for x : int in _base_list.item_count(): + if current == _base_list.get_item_tooltip(x): + var mk : ToolDB.MickeyTool = _tool_db.get_tool_id(x) + if mk: + root = mk.get_control() + break + elif current is Node: + for x : ToolDB.MickeyTool in _tool_db.get_tools(): + if x.has(current): + root = x.get_control() + break + return root + +func _on_tab_rmb(index : int, tab : TabContainer) -> void: + _rmb_menu.execute([index, tab]) + +func left_tab_close(value : Variant) -> void: + _user_tab_close.execute([value, -1]) + +func right_tab_close(value : Variant) -> void: + _user_tab_close.execute([value, 1]) + +func others_tab_close(value : Variant) -> void: + _user_tab_close.execute([value, 0]) + +func _move_item_list(from : int, to : int) -> void: + move_item_container(null, from, to) + +func move_item_container(container : TabContainer, from : int, to : int) -> void: + var vfrom : int = -1 + var vto : int = -1 + + if container == null: + vfrom = from + vto = to + else: + for x : ToolDB.MickeyTool in _tool_db.get_tools(): + if x.get_root() == container: + var _idx : int = x.get_control().get_index() + if _idx == from: + vfrom = x.get_index() + elif _idx == to: + vto = x.get_index() + + if vfrom == -1 or vto == -1: + return + + _base_container.move_container(vfrom, vto) diff --git a/addons/script_splitter/core/editor/godot/manager.gd.uid b/addons/script_splitter/core/editor/godot/manager.gd.uid new file mode 100644 index 00000000..eccf3d07 --- /dev/null +++ b/addons/script_splitter/core/editor/godot/manager.gd.uid @@ -0,0 +1 @@ +uid://blq08yud6jfse diff --git a/addons/script_splitter/core/editor/tools/editor_tool.gd b/addons/script_splitter/core/editor/tools/editor_tool.gd new file mode 100644 index 00000000..22fbe5a9 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/editor_tool.gd @@ -0,0 +1,18 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const MickeyTool = preload("./../../../core/editor/tools/magic/mickey_tool.gd") +const MickeyToolRoute = preload("./../../../core/editor/tools/magic/mickey_tool_route.gd") + +func build(control : Node) -> MickeyTool: + return _build_tool(control) + +func _build_tool(_control : Node) -> MickeyTool: + return null diff --git a/addons/script_splitter/core/editor/tools/editor_tool.gd.uid b/addons/script_splitter/core/editor/tools/editor_tool.gd.uid new file mode 100644 index 00000000..135f612c --- /dev/null +++ b/addons/script_splitter/core/editor/tools/editor_tool.gd.uid @@ -0,0 +1 @@ +uid://byxd23l74ehqq diff --git a/addons/script_splitter/core/editor/tools/helper_editor_tool.gd b/addons/script_splitter/core/editor/tools/helper_editor_tool.gd new file mode 100644 index 00000000..5a6feff6 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/helper_editor_tool.gd @@ -0,0 +1,49 @@ +@tool +extends "./../../../core/editor/tools/editor_tool.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _build_tool(control : Node) -> MickeyTool: + if control is ScriptEditorBase: + return null + if control.name.begins_with("@"): + return null + + var mickey : MickeyTool = null + for x : Node in control.get_children(): + if x is RichTextLabel: + var canvas : VBoxContainer = VBoxContainer.new() + canvas.size_flags_vertical = Control.SIZE_EXPAND_FILL + canvas.size_flags_vertical = Control.SIZE_EXPAND_FILL + + var childs : Array[Node] = control.get_children() + for n : Node in childs: + control.remove_child(n) + canvas.add_child(n) + + canvas.size = control.size + mickey = MickeyToolRoute.new(control, canvas, canvas) + break + return mickey + +func _handler(control : Node) -> MickeyTool: + var mickey : MickeyTool = null + if control is RichTextLabel: + var canvas : VBoxContainer = VBoxContainer.new() + canvas.size_flags_vertical = Control.SIZE_EXPAND_FILL + canvas.size_flags_vertical = Control.SIZE_EXPAND_FILL + + if canvas.get_child_count() < 1: + var childs : Array[Node] = control.get_children() + for n : Node in childs: + control.remove_child(n) + canvas.add_child(n) + + canvas.size = control.size + mickey = MickeyToolRoute.new(control, canvas, canvas) + return mickey diff --git a/addons/script_splitter/core/editor/tools/helper_editor_tool.gd.uid b/addons/script_splitter/core/editor/tools/helper_editor_tool.gd.uid new file mode 100644 index 00000000..78266add --- /dev/null +++ b/addons/script_splitter/core/editor/tools/helper_editor_tool.gd.uid @@ -0,0 +1 @@ +uid://cnor3blarugxa diff --git a/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd b/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd new file mode 100644 index 00000000..5330f1ac --- /dev/null +++ b/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd @@ -0,0 +1,228 @@ +@tool +extends RefCounted +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const Notfy = preload("./../../../../core/util/control.gd") + +signal focus(_tool : Object) +signal new_symbol(symbol : String) +signal clear() + +var _owner : Control = null +var _root_control : Control = null +var _control : Control = null +var _index : int = -1 + +var _queue_free : bool = false + +func set_queue_free(q : bool) -> void: + _queue_free = q + +func is_queue_free() -> bool: + return _queue_free + +func is_valid() -> bool: + for x : Variant in [_owner, _root_control, _control]: + if !is_instance_valid(x) or (x as Node).is_queued_for_deletion() or !(x as Node).is_inside_tree(): + return false + return _owner != get_root() + +func update_metadata(tittle : String, tooltips : String, icon : Texture2D) -> void: + if is_instance_valid(_control): + var parent : Node = _root_control + for __ : int in range(0, 4, 1): + if parent is TabContainer or parent == null: + break + parent = parent.get_parent() + + if parent is TabContainer: + var index : int = _root_control.get_index() + if index > -1 and parent.get_tab_count() > index: + if !tittle.is_empty() and parent.get_tab_title(index) != tittle: + parent.set_tab_title(index, tittle) + _root_control.name = tittle + if !tooltips.is_empty() and parent.get_tab_tooltip(index) != tooltips: + parent.set_tab_tooltip(index, tooltips) + parent.set_tab_icon(index, icon) + +func ochorus(root : Node) -> void: + if is_instance_valid(_root_control) and is_instance_valid(root): + var parent : Node = _root_control.get_parent() + if parent != root: + + _connect_callback(false) + + if parent: + _root_control.reparent(root) + else: + root.add_child(_root_control) + + if _owner == root: + if _root_control.get_index() != _index: + if _owner.get_child_count() > _index: + _owner.move_child(_root_control, _index) + else: + if root is TabContainer: + var tittle_id : int = _root_control.get_index() + if tittle_id > -1 and tittle_id < root.get_tab_count(): + var tl : String = root.get_tab_title(tittle_id) + if tl.is_empty() or (tl.begins_with("@") and "Text" in tl): + root.set_tab_title(tittle_id, "Editor") + + _connect_callback(true) + + _root_control.visible = true + +func trigger_focus() -> void: + focus.emit(self) + +func get_owner() -> Node: + return _owner + +func get_root() -> Node: + if _root_control: + return _root_control.get_parent() + return null + +func get_root_control() -> Node: + if _root_control: + var node : Node = _root_control.get_parent() + if node: + return node.get_parent() + return null + + +func get_control() -> Node: + return _root_control + +func get_gui() -> Node: + return _control + +func has(current_control : Node) -> bool: + return _owner == current_control or _root_control == current_control or _control == current_control or get_root() == current_control + +func _init(owner_control : Control, current_root_control : Control, current_control : Control) -> void: + _owner = owner_control + _root_control = current_root_control + _control = current_control + _index = current_root_control.get_index() + + _owner.tree_exiting.connect(reset) + + for x : Control in [ + _owner, _root_control, _control + ]: + x.set_script(Notfy) + if _owner == x: + x.panic() + if x.has_signal(&"notification"): + if !x.is_connected(&"notification", _on_not): + x.connect(&"notification", _on_not) + + _con_focus(_control, true) + +func _con_focus(n : Node, con : bool) -> void: + if n is Control: + if n.focus_mode != Control.FOCUS_NONE: + if con: + if !_control.gui_input.is_connected(_on_input): + _control.gui_input.connect(_on_input) + else: + if _control.gui_input.is_connected(_on_input): + _control.gui_input.disconnect(_on_input) + for x : Node in n.get_children(): + _con_focus(x, con) + +func _get_callables(gui : Control) -> Array: + return [ + [gui.focus_entered, _i_like_coffe], + #[gui.focus_exited, _i_like_candy], + #[gui.visibility_changed, _i_like_coffe], + ] + +func _connect_callback(con : bool) -> void: + var gui : Control = _control + if gui is VBoxContainer: + gui = gui.get_child(0) + + var arr : Array = _get_callables(gui) + + if gui is CodeEdit: + arr.append([gui.symbol_lookup, _on_symb]) + + if _control.focus_mode != Control.FOCUS_NONE: + _con_focus(_control, con) + + for x : Array in arr: + if con: + if !x[0].is_connected(x[1]): + x[0].connect(x[1]) + else: + if x[0].is_connected(x[1]): + x[0].disconnect(x[1]) + + if con: + if is_instance_valid(gui): + focus.emit.call_deferred(self) + elif is_instance_valid(_control): + _control.modulate = Color.WHITE + +func _on_not(what : int) -> void: + if what == NOTIFICATION_PREDELETE: + reset() + +func get_index() -> int: + if is_instance_valid(_owner) and _owner.is_inside_tree(): + return _owner.get_index() + return -1 + +func _i_like_coffe() -> void: + focus.emit(self) + +func reset() -> void: + for x : Variant in [ + _owner, _root_control, _control + ]: + if is_instance_valid(x): + x.set_script(null) + + if _control is CodeEdit and !_control.is_queued_for_deletion() and _control.get_parent() is VSplitContainer: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + x.script_merge(_control) + break + + ochorus(_owner) + set_queue_free(true) + _owner = null + _root_control = null + _control = null + + clear.emit() + +func _context_update(window : Window, control : Control) -> void: + if is_instance_valid(window) and is_instance_valid(control): + var screen_rect: Rect2 = DisplayServer.screen_get_usable_rect(window.current_screen) + var gvp: Vector2 = control.get_screen_position() + control.get_local_mouse_position() + gvp.y = min(gvp.y, screen_rect.position.y + screen_rect.size.y - window.size.y + 16.0) + gvp.x = min(gvp.x, screen_rect.position.x + screen_rect.size.x - window.size.x + 16.0) + window.set_deferred(&"position", gvp) + +func _on_input(input : InputEvent) -> void: + if input is InputEventMouseMotion: + return + + if input is InputEventMouseButton: + if input.pressed and input.button_index == MOUSE_BUTTON_RIGHT: + for x : Node in _owner.get_children(): + var variant : Node = x + if variant is Window and _control is Control: + _context_update.call_deferred(variant, _control) + trigger_focus() + +func _on_symb(symbol: String, _line : int, _column: int, _edit : CodeEdit = null) -> void: + new_symbol.emit(symbol) diff --git a/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd.uid b/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd.uid new file mode 100644 index 00000000..09663078 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/magic/mickey_tool.gd.uid @@ -0,0 +1 @@ +uid://r4j0eu5er1m4 diff --git a/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd b/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd new file mode 100644 index 00000000..54a80f5a --- /dev/null +++ b/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd @@ -0,0 +1,50 @@ +@tool +extends "./../../../../core/editor/tools/magic/mickey_tool.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func has(current_control : Node) -> bool: + if super(current_control): + return true + for x : Node in current_control.get_children(): + if super(x): + return true + return false + +func ochorus(root : Node) -> void: + if is_instance_valid(_root_control) and is_instance_valid(root): + var parent : Node = _root_control.get_parent() + if parent != root: + + _connect_callback(false) + + if _owner == root: + var childs : Array[Node] = _root_control.get_children() + for n : Node in childs: + _root_control.remove_child(n) + _owner.add_child(n) + _root_control.queue_free() + else: + if parent: + _root_control.reparent(root) + else: + root.add_child(_root_control) + + if root is Control: + _root_control.size = root.size + + if root is TabContainer: + var tittle_id : int = _root_control.get_index() + if tittle_id > -1 and tittle_id < root.get_tab_count(): + var tl : String = root.get_tab_title(tittle_id) + if tl.is_empty() or (tl.begins_with("@") and "Text" in tl): + root.set_tab_title(tittle_id, "Editor") + + _connect_callback(true) + + _root_control.visible = true diff --git a/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd.uid b/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd.uid new file mode 100644 index 00000000..fae7083a --- /dev/null +++ b/addons/script_splitter/core/editor/tools/magic/mickey_tool_route.gd.uid @@ -0,0 +1 @@ +uid://pkj5o6q7sine diff --git a/addons/script_splitter/core/editor/tools/script_editor_tool.gd b/addons/script_splitter/core/editor/tools/script_editor_tool.gd new file mode 100644 index 00000000..60158694 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/script_editor_tool.gd @@ -0,0 +1,26 @@ +@tool +extends "./../../../core/editor/tools/editor_tool.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _build_tool(control : Node) -> MickeyTool: + if control is ScriptEditorBase: + var editor : Control = control.get_base_editor() + var mickey_tool : MickeyTool = null + if editor is CodeEdit: + var rcontrol : Node = editor.get_parent() + if is_instance_valid(rcontrol): + for __ : int in range(5): + if rcontrol == null: + break + elif rcontrol is VSplitContainer: + mickey_tool = MickeyTool.new(rcontrol.get_parent(), rcontrol, editor) + break + rcontrol = rcontrol.get_parent() + return mickey_tool + return null diff --git a/addons/script_splitter/core/editor/tools/script_editor_tool.gd.uid b/addons/script_splitter/core/editor/tools/script_editor_tool.gd.uid new file mode 100644 index 00000000..c2a9cc9f --- /dev/null +++ b/addons/script_splitter/core/editor/tools/script_editor_tool.gd.uid @@ -0,0 +1 @@ +uid://c5mrlc852aghg diff --git a/addons/script_splitter/core/editor/tools/text_editor_tool.gd b/addons/script_splitter/core/editor/tools/text_editor_tool.gd new file mode 100644 index 00000000..1d51c4e6 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/text_editor_tool.gd @@ -0,0 +1,22 @@ +@tool +extends "./../../../core/editor/tools/editor_tool.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _build_tool(control : Node) -> MickeyTool: + if control is ScriptEditorBase: + var editor : Control = control.get_base_editor() + var mickey_tool : MickeyTool = null + if editor is CodeEdit: + var parent : Node = control.get_parent() + if parent != null and parent.is_node_ready() and !control.get_parent() is VSplitContainer: + mickey_tool = MickeyTool.new(control, editor, editor) + else: + mickey_tool = MickeyTool.new(control, editor, editor) + return mickey_tool + return null diff --git a/addons/script_splitter/core/editor/tools/text_editor_tool.gd.uid b/addons/script_splitter/core/editor/tools/text_editor_tool.gd.uid new file mode 100644 index 00000000..fd57ac15 --- /dev/null +++ b/addons/script_splitter/core/editor/tools/text_editor_tool.gd.uid @@ -0,0 +1 @@ +uid://xkgq82knloas diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg new file mode 100644 index 00000000..4e3a5141 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg.import b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg.import new file mode 100644 index 00000000..7a4be264 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c042di3o7rqml" +path="res://.godot/imported/MultiSpliter.svg-3fac225927d0b135f9e22aa6666d3a55.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliter.svg" +dest_files=["res://.godot/imported/MultiSpliter.svg-3fac225927d0b135f9e22aa6666d3a55.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg new file mode 100644 index 00000000..28877b84 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg.import b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg.import new file mode 100644 index 00000000..2238352f --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1oshw4o6jues" +path="res://.godot/imported/MultiSpliterButton.svg-2d0a07173fe1e75a33d019b041262a7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterButton.svg" +dest_files=["res://.godot/imported/MultiSpliterButton.svg-2d0a07173fe1e75a33d019b041262a7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg new file mode 100644 index 00000000..09251a41 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + diff --git a/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg.import b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg.import new file mode 100644 index 00000000..dcaa122b --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvg5cjdqtdpd7" +path="res://.godot/imported/MultiSpliterItem.svg-1c00cf564bbcca09a4b17a958816995f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/script_splitter/core/ui/multi_split_container/icon/MultiSpliterItem.svg" +dest_files=["res://.godot/imported/MultiSpliterItem.svg-1c00cf564bbcca09a4b17a958816995f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd b/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd new file mode 100644 index 00000000..fb1af1a1 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd @@ -0,0 +1,998 @@ +@tool +@icon("icon/MultiSpliter.svg") +extends Container +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# https://github.com/CodeNameTwister/Multi-Split-Container +# +# Multi-Split-Container addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const SplitContainerItem : Script = preload("split_container_item.gd") +const SplitButton : Texture = preload("icon/MultiSpliterButton.svg") + +@export_category("Multi-Split Settings") +## Max columns by rows, after added childs this is eparated by row group by columns size! +## [br][br] +## if this value is 0, will not create rows spliters. +@export_range(0.0, 1000.0, 1.0) var max_columns : int = 0: + set(e): + max_columns = maxi(0, e) + + if Engine.is_editor_hint(): + for x : int in range(separators_line_offsets.size()): + separators_line_offsets[x] = 0.0 + for x : LineSep in _separators: + x.queue_free() + + _separators.clear() + _first = true + update() + +@export_group("Line Separator", "separator_line") +## Line separator size. +@export var separator_line_size : float = 4.0: + set(e): + separator_line_size = max(e, 0.0) + update() + +## Separator line color. +@export var separator_line_color : Color = Color.MAGENTA: + set(e): + separator_line_color = e + if separator_line_color == Color.MAGENTA: # That color reminds me of texture not found errors. + var root = EditorInterface.get_base_control() + separator_line_color = root.get_theme_color("base_color", "Editor") + update() + +## Separator line visibility. +@export var separator_line_visible : bool = true: + set(e): + separator_line_visible = e + for l : LineSep in _separators: + l.visible = separator_line_visible + +@export_subgroup("Behaviour", "behaviour_") +## Enable function for auto expand lines container on inside focus. +@export var behaviour_expand_on_focus : bool = true + +## Enable function for auto expand lines container on double click in the line. +@export var behaviour_expand_on_double_click : bool = true: + set(e): + behaviour_expand_on_double_click = e + for l : LineSep in _separators: + l.double_click_handler = behaviour_expand_on_double_click + +## Enable movement by touching line. +@export var behaviour_can_move_by_line : bool = true: + set(e): + behaviour_can_move_by_line = e + for l : LineSep in _separators: + l.draggable = behaviour_can_move_by_line + +## This allow expand you current focused container if you shrunk it. +@export var behaviour_can_expand_focus_same_container : bool = false + +## Enable smooth when expand container. +@export var behaviour_expand_smoothed : bool = true: + set(e): + behaviour_expand_smoothed = e + if !e: + if _tween and _tween.is_running(): + _tween.kill() + _tween = null + +## Time speed duration for reset expand container. +@export_range(0.01, 1000.0, 0.01) var behaviour_expand_smoothed_time : float = 0.24: + set(e): + behaviour_expand_smoothed_time = maxf(0.01, e) + if _tween and _tween.is_running(): + _tween.kill() + _tween = null + +## Custom initial offset for separator lines. (TODO: Still Working here!) +@export var separators_line_offsets : Array[float] : + set(e): + separators_line_offsets = e + + if Engine.is_editor_hint(): + if separators_line_offsets.size() != _separators.size(): + separators_line_offsets.resize(_separators.size()) + update() + +@export_subgroup("Drag Button", "drag_button") + +## Set if drag button always be visible (Useful for test button size) +@export var drag_button_always_visible : bool = false: + set(e): + drag_button_always_visible = e + + var min_visible_drag_button : float = 0.0 + if drag_button_always_visible: + min_visible_drag_button = 0.4 + + for l : LineSep in _separators: + if l.button: + l.button.modulate.a = 0.0 + l.button.min_no_focus_transparense = min_visible_drag_button + +## Min size for drag button visible on split lines. +@export_range(1.0, 200.0, 0.1) var drag_button_size : float = 24.0: + set(e): + drag_button_size = e + update() + +## Modulate color for the drag button. +@export var drag_button_modulate : Color = Color.MAGENTA: + set(e): + drag_button_modulate = e + if drag_button_modulate == Color.MAGENTA: + if Engine.is_editor_hint(): + var root : Control = EditorInterface.get_base_control() + drag_button_modulate = root.get_theme_color("base_color", "Editor").lightened(0.5) + update() + +## Change default drag button icon. +@export var drag_button_icon : Texture = null: + set(e): + drag_button_icon = e + update() + +var _separators : Array[LineSep] = [] +var _last_container_focus : Node = null +var _frame : int = 1 +var _first : bool = true +var _tween : Tween = null + +func get_separators() -> Array[LineSep]: + return _separators + +## Get line begin offset limit. +func get_line_seperator_left_offset_limit(index : int) -> float: + if index < _separators.size(): + var line_sep : LineSep = _separators[index] + if !line_sep.is_vertical: + if index < 1: + return -_separators[index].initial_position.x + var next : LineSep = _separators[index - 1] + return (next.initial_position.x + (next.size.x/2.0)) - _separators[index].initial_position.x + else: + if index < 1: + return -_separators[index].initial_position.y + var next : LineSep = _separators[index - 1] + return (next.initial_position.y + (next.size.y/2.0)) - _separators[index].initial_position.y + push_warning("[PLUGIN] Not valid index for line separator!") + return 0.0 + +## Get line end offset limit. +func get_line_seperator_right_offset_limit(index : int) -> float: + if index < _separators.size(): + var line_sep : LineSep = _separators[index] + if !line_sep.is_vertical: + if index + 1 == _separators.size(): + return (size.x/2.0) -_separators[index].initial_position.x + var current : LineSep = _separators[index] + return (_separators[index + 1].initial_position.x - current.initial_position.x + (current.size.x/2.0)) + else: + if index + 1 == _separators.size(): + return size.x -_separators[index].initial_position.y + var current : LineSep = _separators[index] + return (_separators[index + 1].initial_position.y - current.initial_position.y + (current.size.y/2.0)) + push_warning("[PLUGIN] Not valid index for line separator!") + return 0.0 + +# This is function is util when you want expand or constraint manualy offset. +## Update offset of the line +func update_line_separator_offset(index : int, offset : float) -> void: + var line_sep : LineSep = _separators[index] + line_sep.offset = offset + line_sep.force_update() + +## Get total line count. +func get_line_separator_count() -> int: + return _separators.size() + +## Get Line reference by index, see get_line_separator_count() +func get_line_separator(index : int) -> LineSep: + return _separators[index] + +## Get if line separator is vertical. +func is_vertical_line_separator(index : int) -> bool: + if index < _separators.size(): + return _separators[index].is_vertical + push_warning("[PLUGIN] Not valid index for line separator!") + return false + +## Expand splited container by index container. +func expand_splited_container(node : Node) -> void: + var same : bool = _last_container_focus == node + if same and !behaviour_can_expand_focus_same_container: + return + + _last_container_focus = node + + if !behaviour_expand_on_focus: + return + + if _tween and _tween.is_running(): + if same: + return + _tween.kill() + _tween = null + + var top_lines : Array[LineSep] = [] + var bottom_lines : Array[LineSep] = [] + + var update_required : bool = false + + for line : LineSep in _separators: + if node in line.top_items: + update_required = update_required or line.offset < 0.0 + top_lines.append(line) + elif node in line.bottom_items: + update_required = update_required or line.offset > 0.0 + bottom_lines.append(line) + + if update_required: + if behaviour_expand_smoothed: + _tween = get_tree().create_tween() + _tween.tween_method(_reset_expanded_lines.bind(top_lines, bottom_lines), 0.0, 1.0, behaviour_expand_smoothed_time) + else: + _reset_expanded_lines(1.0, top_lines, bottom_lines) + +func _reset_expanded_lines(_lerp : float, top_lines : Array[LineSep], bottom_lines : Array[LineSep]) -> void: + for iline : int in range(top_lines.size() - 1, -1, -1): + var line : LineSep = top_lines[iline] + if is_instance_valid(line): + if line.offset < 0.0: + line.offset = lerp(line.offset, 0.0, _lerp) + else: + top_lines.remove_at(iline) + + for iline : int in range(bottom_lines.size() - 1, -1, -1): + var line : LineSep = bottom_lines[iline] + if is_instance_valid(line): + if line.offset > 0.0: + line.offset = lerp(line.offset, 0.0, _lerp) + else: + bottom_lines.remove_at(iline) + + for line : LineSep in top_lines: + line.force_update() + for line : LineSep in bottom_lines: + line.force_update() + +## Get initial position of a separator line. +func get_line_separator_initial_position(index : int) -> Vector2: + if index < _separators.size(): + return _separators[index].initial_position + push_warning("[PLUGIN] Not valid index for line separator!") + return Vector2.ZERO + +class DragButton extends Button: + var _frm : float = 0.0 + var _line_sep : LineSep = null + var _is_pressed : bool = false + + var is_hover : bool = false + + var _hover : Array[bool] = [false, false] + + var min_no_focus_transparense : float = 0.0: + set(e): + min_no_focus_transparense = e + modulate.a = maxf(modulate.a, min_no_focus_transparense) + + static var DEFAULT_STYLE : StyleBox = null + + func set_drag_icon(new_icon : Texture) -> void: + if icon != new_icon: + if new_icon == null: + icon = SplitButton + return + icon = new_icon + + func update_gui() -> void: + if !_line_sep: + return + + if _line_sep.is_vertical: + _line_sep.mouse_default_cursor_shape = Control.CURSOR_VSPLIT + mouse_default_cursor_shape = Control.CURSOR_VSPLIT + else: + _line_sep.mouse_default_cursor_shape = Control.CURSOR_HSPLIT + mouse_default_cursor_shape = Control.CURSOR_HSPLIT + + func set_line(line_sep : LineSep) -> void: + if _line_sep: + if _line_sep.mouse_entered.is_connected(_on_enter): + _line_sep.mouse_entered.disconnect(_on_enter) + if _line_sep.mouse_exited.is_connected(_on_exit): + _line_sep.mouse_exited.disconnect(_on_exit) + if _line_sep.gui_input.is_connected(_on_input): + _line_sep.gui_input.disconnect(_on_input) + + _line_sep = line_sep + + if _line_sep: + if !_line_sep.mouse_entered.is_connected(_on_enter): + _line_sep.mouse_entered.connect(_on_enter.bind(1)) + if !_line_sep.mouse_exited.is_connected(_on_exit): + _line_sep.mouse_exited.connect(_on_exit.bind(1)) + if !_line_sep.gui_input.is_connected(_on_input): + _line_sep.gui_input.connect(_on_input) + + + func _init(line_sep : LineSep = null) -> void: + modulate.a = 0.0 + + set_line(line_sep) + + button_down.connect(_on_press) + button_up.connect(_out_press) + mouse_entered.connect(_on_enter.bind(0)) + mouse_exited.connect(_on_exit.bind(0)) + + gui_input.connect(_custom_input) + + icon = SplitButton + icon_alignment = HORIZONTAL_ALIGNMENT_CENTER + vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER + expand_icon = true + + if null != icon: + flat = true + + if DEFAULT_STYLE == null: + DEFAULT_STYLE = StyleBoxEmpty.new() + + focus_mode = Control.FOCUS_CLICK + + set(&"theme_override_styles/focus", DEFAULT_STYLE) + set(&"theme_override_styles/disabled_mirrored", DEFAULT_STYLE) + set(&"theme_override_styles/disabled", DEFAULT_STYLE) + set(&"theme_override_styles/hover_pressed_mirrored", DEFAULT_STYLE) + set(&"theme_override_styles/hover_pressed", DEFAULT_STYLE) + set(&"theme_override_styles/hover_mirrored", DEFAULT_STYLE) + set(&"theme_override_styles/hover", DEFAULT_STYLE) + set(&"theme_override_styles/pressed_mirrored", DEFAULT_STYLE) + set(&"theme_override_styles/pressed", DEFAULT_STYLE) + set(&"theme_override_styles/normal_mirrored", DEFAULT_STYLE) + set(&"theme_override_styles/normal", DEFAULT_STYLE) + + z_as_relative = true + z_index = 20 + + update_gui() + + func _custom_input(e : InputEvent) -> void: + if e is InputEventMouseButton: + if e.pressed and e.double_click: + get_tree().call_group(&"ScriptSplitter", &"swap", get_parent()) + + func _on_input(e : InputEvent) -> void: + if e is InputEventMouseButton: + if e.pressed and e.double_click: + if _line_sep and _line_sep.double_click_handler: + _line_sep.offset = 0.0 + _line_sep.offset_updated.emit() + elif e.pressed and _line_sep.draggable and e.button_index == 1: + button_down.emit() + elif !e.pressed and _line_sep.draggable and e.button_index == 1: + button_up.emit() + + func set_line_sep_reference(ref : LineSep) -> void: + _line_sep = ref + + func _ready() -> void: + set_process(false) + + func _on_enter(x : int = 0) -> void: + _hover[x] = true + + _frm = 0.0 + modulate.a = 1.0 + is_hover = true + set_process(true) + + func _on_exit(x : int = 0) -> void: + _hover[x] = false + + for h : bool in _hover: + if h != false: + return + + _frm = 0.0 + modulate.a = 1.0 + is_hover = false + set_process(true) + + func _on_press() -> void: + _is_pressed = true + _frm = 0.0 + modulate.a = 1.0 + set_process(true) + + func _out_press() -> void: + _is_pressed = false + set_process(true) + + func _process(delta : float) -> void: + if !has_focus() and !is_hover: + _frm += delta * 0.4 + if _frm >= 1.0: + _frm = 1.0 + set_process(false) + modulate.a = lerp(modulate.a, min_no_focus_transparense, _frm) + if _is_pressed: + var mpos : Vector2 = _line_sep.get_parent().get_local_mouse_position() + if mpos != get_rect().get_center(): + _line_sep.update_offset_by_position(mpos) + +class UndoredoSplit extends RefCounted: + var object : SplitContainerItem = null + var c_objects : Array[Node] = [] + +class LineSep extends ColorRect: + signal offset_updated() + + var top_lines : Array[LineSep] = [] + var bottom_lines : Array[LineSep] = [] + + var top_items : Array[Control] = [] + var bottom_items : Array[Control] = [] + + var is_vertical : bool = false: + set(e): + is_vertical = e + + if button: + button.update_gui() + + var row : int = 0 + + var initial_position : Vector2 = Vector2.ZERO + var offset : float = 0.0 + + var min_size_offset : float = 0.0 + + var prev_line : LineSep = null + var next_line : LineSep = null + + var button : DragButton = null + + var double_click_handler : bool = true + var draggable : bool = true + + func set_next_line(next : LineSep = null) -> void: + next_line = next + next.prev_line = self + + func clear() -> void: + top_items.clear() + bottom_items.clear() + top_lines.clear() + bottom_lines.clear() + + func reset() -> void: + position = initial_position + update_items() + + func update_items() -> void: + if is_vertical: + for item : Control in top_items: + item.size.y = position.y - item.position.y + if !prev_line: + item.position.y = 0.0 + + for item : Control in bottom_items: + item.position.y = position.y + size.y + + if next_line: + item.size.y = next_line.position.y - item.position.y + else: + item.size.y = get_parent().size.y - item.position.y + else: + for item : Control in top_items: + item.size.x = position.x - item.position.x + (size.x / 2.0) - 2.0 + if !prev_line: + item.position.x = 0.0 + + for item : Control in bottom_items: + var diff : float = position.x + (size.x / 2.0) + 2.0 + item.position.x = diff + + if next_line: + item.size.x = next_line.position.x - item.position.x + else: + item.size.x = get_parent().size.x - item.position.x + + func force_update() -> void: + update_offset_by_position(initial_position + Vector2(offset * int(!is_vertical), offset * int(is_vertical))) + + func get_current_position() -> Vector2: + return initial_position + Vector2(offset * int(!is_vertical), offset * int(is_vertical)) + + func update_offset_by_position(vpos : Vector2) -> void: + if is_vertical: + min_size_offset = 0.0 + for x : Control in top_items: + min_size_offset = maxf(min_size_offset, x.get_minimum_size().y) + if prev_line: + prev_line.min_size_offset = 0.0 + for x : Control in prev_line.bottom_items: + prev_line.min_size_offset = maxf(prev_line.min_size_offset, x.get_minimum_size().y) + + offset = vpos.y - initial_position.y + offset = minf(offset, get_parent().size.y - (initial_position.y + size.y + min_size_offset)) + offset = maxf(offset, -(initial_position.y - min_size_offset)) + + if next_line: + var val : float = next_line.position.y - (initial_position.y + size.y + min_size_offset) + if offset > val: + offset = val + else: + var val : float = get_parent().size.y - (initial_position.y + (size.y / 2.0) + min_size_offset) + if offset > val: + offset = val + if prev_line: + var val : float = -(initial_position.y - (prev_line.position.y + prev_line.size.y + prev_line.min_size_offset)) + + if offset < val: + offset = val + else: + var top_size_offset : float = 0.0 + for x : Control in top_items: + top_size_offset = maxf(top_size_offset, x.get_minimum_size().y) + offset = maxf(offset, top_size_offset-initial_position.y) + + position.y = initial_position.y + offset + + for line : LineSep in top_lines: + line.size.y = position.y - line.position.y + + for line : LineSep in bottom_lines: + line.position.y = position.y + size.y + + if next_line: + line.size.y = next_line.position.y - line.position.y + else: + line.size.y = get_parent().size.y - line.position.y + else: + min_size_offset = 0.0 + for x : Control in bottom_items: + min_size_offset = maxf(min_size_offset, x.get_minimum_size().x) + + if prev_line: + prev_line.min_size_offset = 0.0 + for x : Control in prev_line.bottom_items: + prev_line.min_size_offset = maxf(prev_line.min_size_offset, x.get_minimum_size().x) + + offset = vpos.x - initial_position.x + offset = minf(offset, get_parent().size.x - (initial_position.x + size.x + min_size_offset)) + offset = maxf(offset, -initial_position.x) + + if next_line: + var val : float = next_line.position.x - (initial_position.x + size.x + min_size_offset) + if offset > val: + offset = val + else: + var val : float = get_parent().size.x - (initial_position.x + (size.x/2.0) + min_size_offset) + if offset > val: + offset = val + if prev_line: + var val : float = -(initial_position.x - (prev_line.position.x + prev_line.size.x + prev_line.min_size_offset)) + + if offset < val: + offset = val + else: + var top_size_offset : float = 0.0 + for x : Control in top_items: + top_size_offset = maxf(top_size_offset, x.get_minimum_size().x) + offset = maxf(offset, top_size_offset-initial_position.x) + + position.x = initial_position.x + offset + update_items() + + func _draw() -> void: + update() + + func update() -> void: + button.rotation_degrees = 90.0 * int(is_vertical) + button.pivot_offset = button.size / 2.0 + button.position = size / 2.0 - button.pivot_offset + + + + func _init() -> void: + color = Color.RED + + func _ready() -> void: + name = "SplitLine" + if button == null: + button = DragButton.new(self) + add_child(button, false, Node.INTERNAL_MODE_BACK) + +func _test() -> void: + queue_redraw() + +func _init() -> void: + child_entered_tree.connect(_on_enter) + child_exiting_tree.connect(_on_exiting) + +func update() -> void: + set_process(true) + +func _create_separator() -> Control: + var line_sep : LineSep = LineSep.new() + line_sep.offset_updated.connect(update) + return line_sep + +func _undoredo_undo(ur : UndoredoSplit) -> void: + if !is_instance_valid(ur): + return + + var split : SplitContainerItem = ur.object + if is_instance_valid(split): + if split.get_parent() == self: + ur.c_objects = split.get_children() + for x : Node in ur.c_objects: + split.remove_child(x) + if x is Control: + x.visible = false + add_child(x) + if is_instance_valid(split) and split.get_parent() == self: + remove_child(split) + +func _update() -> void: + var items : Array[Control] = [] + for x : Node in get_children(): + if is_instance_valid(x) and x is Control: + if x.visible and !x.is_queued_for_deletion(): + if x is SplitContainerItem: + if x.get_child_count() > 0: + var _is_visible : bool = false + for y : Node in x.get_children(): + if y is Control and y.visible: + _is_visible = true + break + if !_is_visible: + continue + else: + x.queue_free() + continue + elif x is DragButton or x is LineSep: + x.queue_free() + continue + else: + var container : SplitContainerItem = SplitContainerItem.new() + + add_child(container, true) + + x.reparent(container) + x = container + + + x.size_flags_horizontal = Control.SIZE_FILL + x.size_flags_vertical = Control.SIZE_FILL + x.clip_contents = true + x.custom_minimum_size = Vector2.ZERO + items.append(x) + + var totals : int = items.size() + var rows : int = 0 + + if max_columns > 0: + var _totals : int = totals + rows = 0 + while _totals > max_columns: + _totals -= max_columns + rows += 1 + totals -= rows + + if totals < 1: + for x : int in range(0, _separators.size(), 1): + _separators[x].queue_free() + _separators[x] = null + _separators.clear() + + for x : Control in items: + x.position = Vector2.ZERO + x.size = get_rect().size + return + else: + if separator_line_size <= 0.0: + for x : int in range(0, _separators.size(), 1): + _separators[x].queue_free() + _separators[x] = null + _separators.clear() + else: + var sep : int = totals - 1 + rows + for x : int in range(sep, _separators.size(), 1): + _separators[x].queue_free() + _separators[x] = null + _separators.resize(sep) + for x : int in range(0, _separators.size(), 1): + if _separators[x] == null: + _separators[x] = _create_separator() + + rows += 1 + if max_columns > 1: + if totals > max_columns: + totals = max_columns + + var rect_size : Vector2 = get_rect().size + var start_position : Vector2 = Vector2.ZERO + + var size_split : Vector2 = (rect_size / Vector2(totals, rows)) + + var size_sep : Vector2 = Vector2.ONE * separator_line_size + + if totals > 1: + size_sep = (size_sep / (totals - 1)) + + var item_size : Vector2 = Vector2(size_split.x, size_split.y) + var line_size : Vector2 = Vector2(separator_line_size, item_size.y) + + var total_items : int = items.size() + + var vpos : Vector2 = Vector2.ZERO + var current_row : int = 0 + + var item_index : int = 0 + + var last_vline : LineSep = null + var last_hline : LineSep = null + + for x : Control in items: + x.position = Vector2.ZERO + x.size = x.get_minimum_size() + + for z : int in range(_separators.size()): + var x : LineSep = _separators[z] + + x.clear() + + start_position.x += 1 + + if 0 < max_columns and start_position.x + 1 > max_columns: + total_items -= max_columns + start_position.x = 0.0 + start_position.y += 1.0 + current_row += 1 + if total_items <= max_columns and total_items > 0: + size_split = (rect_size / Vector2(total_items, rows)) + if total_items == 1: + size_sep = Vector2.ONE * separator_line_size + else: + size_sep = ((Vector2.ONE * separator_line_size) / (total_items - 1)) + item_size = Vector2(size_split.x, size_split.y) + line_size = Vector2(separator_line_size, rect_size.y - x.position.y) + + vpos = Vector2(0.0, start_position.y) * item_size + x.is_vertical = true + + if x.get_parent() == null: + add_child(x, false, Node.INTERNAL_MODE_BACK) + + + x.row = current_row + + if items.size() > 0: + var it : int = mini(item_index, items.size() - 1) + var min_size : float = 0.0 + + var _has : bool = false + + for y : int in range(z - 1, -1, -1): + if it > -1: + var item : Control = items[it] + x.top_items.append(item) + min_size = maxf(item.get_minimum_size().y, min_size) + it -= 1 + + var ln : LineSep = _separators[y] + if ln.is_vertical: + _has = true + break + x.top_lines.append(ln) + if !_has: + for _it : int in range(it, -1, -1): + var item : Control = items[it] + x.top_items.append(item) + + if item_index + 1 < items.size(): + it = item_index + 1 + _has = false + for y : int in range(z + 1, _separators.size(), 1): + if it < items.size(): + var item : Control = items[it] + x.bottom_items.append(item) + it += 1 + + var ln : LineSep = _separators[y] + if ln.is_vertical: + _has = true + break + x.bottom_lines.append(ln) + if !_has: + for _it : int in range(it, items.size(), 1): + var item : Control = items[_it] + x.bottom_items.append(item) + + var vline_size : Vector2 = Vector2(rect_size.x, separator_line_size) + + x.initial_position = vpos + x.initial_position.y -= (vline_size.y) / 2.0 + x.position = x.initial_position + + x.button.size = Vector2(drag_button_size, drag_button_size) + + x.set(&"size", vline_size) + x.update() + + if last_vline: + last_vline.set_next_line(x) + + last_vline = x + last_hline = null + item_index += 1 + continue + + vpos = start_position * item_size + + if x.get_parent() == null: + add_child(x, false, Node.INTERNAL_MODE_BACK) + + + if item_index < items.size(): + var item : Control = items[item_index] + x.top_items.append(item) + item_index += 1 + if item_index < items.size(): + if z + 1 < _separators.size(): + if !_separators[z].is_vertical: + x.bottom_items.append(items[item_index]) + else: + x.bottom_items.append(items[item_index]) + + x.initial_position = vpos + x.initial_position.x -= (line_size.x) / 2.0 + + x.button.size = Vector2(drag_button_size, drag_button_size) + + x.row = current_row + x.position = x.initial_position + + x.set(&"size", line_size) + x.update() + + if last_hline: + last_hline.set_next_line(x) + last_hline = x + + for x : Control in items: + x.size = size + + var min_visible_drag_button : float = 0.0 + if drag_button_always_visible: + min_visible_drag_button = 0.4 + + if _first: + for l : LineSep in _separators: + l.visible = separator_line_visible + l.color = separator_line_color + l.double_click_handler = behaviour_expand_on_double_click + l.button.self_modulate = drag_button_modulate + l.button.min_no_focus_transparense = min_visible_drag_button + l.button.set_drag_icon(drag_button_icon) + l.draggable = behaviour_can_move_by_line + + l.reset() + + else: + if separators_line_offsets.size() > 0: + for l : int in range(0, _separators.size(), 1): + if l < separators_line_offsets.size(): + _separators[l].offset = separators_line_offsets[l] + continue + break + + for l : LineSep in _separators: + l.visible = separator_line_visible + l.color = separator_line_color + l.double_click_handler = behaviour_expand_on_double_click + l.button.self_modulate = drag_button_modulate + l.button.min_no_focus_transparense = min_visible_drag_button + l.button.set_drag_icon(drag_button_icon) + l.draggable = behaviour_can_move_by_line + + l.force_update() + + if !Engine.is_editor_hint(): + separators_line_offsets.clear() + else: + for l : int in range(0, _separators.size(), 1): + if l < separators_line_offsets.size(): + separators_line_offsets[l] = _separators[l].offset + continue + break + +func _on_enter(n : Node) -> void: + n.is_inside_tree() + if n is SplitContainerItem or (n is Control and !Engine.is_editor_hint()): + if !n.visibility_changed.is_connected(_on_visible): + n.visibility_changed.connect(_on_visible) + if is_node_ready(): + for x : int in range(separators_line_offsets.size()): + separators_line_offsets[x] = 0.0 + update() + +func _on_visible() -> void: + update() + +func _on_exiting(n : Node) -> void: + if n is SplitContainerItem or (n is Control and !Engine.is_editor_hint()): + if is_node_ready(): + for x : int in range(separators_line_offsets.size()): + separators_line_offsets[x] = 0.0 + for x : LineSep in _separators: + x.offset = 0.0 + if n.visibility_changed.is_connected(_on_visible): + n.visibility_changed.disconnect(_on_visible) + update() + +func _process(__ : float) -> void: + if is_node_ready(): + if _frame > 0: + _frame -= 1 + return + _update() + if _first: + _first = false + else: + set_process(false) + +func _on_exiting_tree() -> void: + var vp : Viewport = get_viewport() + if vp and vp.size_changed.is_connected(update): + vp.size_changed.disconnect(update) + + var parent : Node = get_parent() + if parent is Control: + if parent.item_rect_changed.is_connected(update): + parent.item_rect_changed.disconnect(update) + +func _enter_tree() -> void: + var vp : Viewport = get_viewport() + if vp and !vp.size_changed.is_connected(update): + vp.size_changed.connect(update) + + var parent : Node = get_parent() + if parent is Control: + if !parent.item_rect_changed.is_connected(update): + parent.item_rect_changed.connect(update) + + if !tree_exiting.is_connected(_on_exiting_tree): + tree_exiting.connect(_on_exiting_tree) + +func _on_draw() -> void: + update() + +func _ready() -> void: + separator_line_color = separator_line_color + drag_button_modulate = drag_button_modulate + + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical =Control.SIZE_EXPAND_FILL + + set_deferred(&"anchor_left", 0.0) + set_deferred(&"anchor_top", 0.0) + set_deferred(&"anchor_bottom", 1.0) + set_deferred(&"anchor_right", 1.0) + + if Engine.is_editor_hint(): + draw.connect(_on_draw) + + if _first: + update() diff --git a/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd.uid b/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd.uid new file mode 100644 index 00000000..0b4003b0 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/multi_split_container.gd.uid @@ -0,0 +1 @@ +uid://sffqwro3flkc diff --git a/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd b/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd new file mode 100644 index 00000000..223b613d --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd @@ -0,0 +1,69 @@ +@tool +@icon("icon/MultiSpliterItem.svg") +extends Control +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# https://github.com/CodeNameTwister/Multi-Split-Container +# +# Multi-Split-Container addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +var focus_handler : bool = false + +## Expand if tight by spliter +func show_splited_container() -> void: + var parent : Node = get_parent() + if parent.has_method(&"expand_splited_container"): + parent.call(&"expand_splited_container", self) + + +func _ready() -> void: + set_process(false) + + size_flags_horizontal = Control.SIZE_FILL + size_flags_vertical = Control.SIZE_FILL + + set_deferred(&"anchor_left", 0.0) + set_deferred(&"anchor_top", 0.0) + set_deferred(&"anchor_bottom", 1.0) + set_deferred(&"anchor_right", 1.0) + +func _init() -> void: + name = "SplitContainerItem" + + child_exiting_tree.connect(_on_child_exiting_tree) + child_entered_tree.connect(_on_child_entered_tree) + +func _on_visible() -> void: + var _visible : bool = false + for x : Node in get_children(): + if x is Control: + if x.visible: + _visible = true + break + visible = _visible + +func _on_child_entered_tree(n : Node) -> void: + if n is Control: + n.size = size + n.set_anchor(SIDE_LEFT, 0.0) + n.set_anchor(SIDE_RIGHT, 1.0) + n.set_anchor(SIDE_TOP, 0.0) + n.set_anchor(SIDE_BOTTOM, 1.0) + if !n.visibility_changed.is_connected(_on_visible): + n.visibility_changed.connect(_on_visible) + +func _disconnect(n : Node) -> void: + if n is Control: + if n.visibility_changed.is_connected(_on_visible): + n.visibility_changed.disconnect(_on_visible) + for x : Node in n.get_children(): + _disconnect(x) + +func _on_child_exiting_tree(n : Node) -> void: + _disconnect(n) + +func _enter_tree() -> void: + var c : Node = get_parent() + if c is Control: + size = c.size diff --git a/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd.uid b/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd.uid new file mode 100644 index 00000000..0596a8d9 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/split_container_item.gd.uid @@ -0,0 +1 @@ +uid://rh6vq7m1qfkr diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd b/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd new file mode 100644 index 00000000..ceedb816 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd @@ -0,0 +1,24 @@ +@tool +extends Label + +func _ready() -> void: + add_to_group(&"SP_TAB_BUTTON") + +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: + var btn : Node = get_parent().get_child(0) + if btn is Button: + btn.pressed.emit() + +func _get_drag_data(__ : Vector2) -> Variant: + return owner.button_main._get_drag_data(__) + +func _drop_data(_at_position: Vector2, data: Variant) -> void: + owner.button_main._drop_data(_at_position, data) + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + return owner.button_main._can_drop_data(at_position, data) + +func get_selected_color() -> Color: + return owner.get_selected_color() diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd.uid new file mode 100644 index 00000000..7022c818 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/changes.gd.uid @@ -0,0 +1 @@ +uid://bkj4lec06udg5 diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/container.gd b/addons/script_splitter/core/ui/multi_split_container/taby/container.gd new file mode 100644 index 00000000..713a4d1e --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/container.gd @@ -0,0 +1,384 @@ +@tool +extends PanelContainer +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const TAB = preload("./../../../../core/ui/multi_split_container/taby/tab.tscn") +const TIME_WAIT : float = 0.35 + +const MAX_COLLAPSED : int = 6 + +@export var container : Control = null +var _dlt : float = 0.0 +var _try : int = 0 + +var buttons : Array[Control] = [] +var hbox : Array[HBoxContainer] = [] +var pins : PackedStringArray = [] + +var _enable_update : bool = true + +var _reference : TabBar = null + +var _select_color : Color = Color.CADET_BLUE: + set = set_select_color + +var _updating : bool = false + +var style : StyleBox = null + +var _behaviour_collapsed : int = MAX_COLLAPSED: + set(e): + _behaviour_collapsed = mini(maxi(0, e), MAX_COLLAPSED) + +func _enter_tree() -> void: + modulate.a = 0.0 + z_index = 10 + get_tree().create_tween().tween_property(self, "modulate:a", 1.0, 0.3) + + _setup() + +func _exit_tree() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func _on_change() -> void: + var dt : Array = [ + "plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/selected_color" + ] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["_select_color", "plugin/script_splitter/behaviour/refresh_warnings_on_saveplugin/script_splitter/editor/list/selected_color"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + for x : Variant in hbox: + if is_instance_valid(x): + if x.get_parent() == null: + x.queue_free() + for x : Variant in buttons: + if is_instance_valid(x): + if x.get_parent() == null: + x.queue_free() + +func _on_pressed(btn : Button) -> void: + if is_instance_valid(_reference): + for x : int in _reference.tab_count: + if _reference.get_tab_tooltip(x) == btn.tooltip_text: + _reference.current_tab = x + #_reference.tab_clicked.emit(x) + _reference.tab_clicked.emit(x) + +func _on_gui_pressed(input : InputEvent, btn : Button) -> void: + if input.is_pressed(): + if is_instance_valid(_reference): + for x : int in _reference.tab_count: + if _reference.get_tab_tooltip(x) == btn.tooltip_text: + if input is InputEventMouseButton: + if input.button_index == MOUSE_BUTTON_RIGHT: + _reference.tab_selected.emit(x) + _reference.tab_rmb_clicked.emit(x) + return + elif input.button_index == MOUSE_BUTTON_MIDDLE: + _reference.tab_close_pressed.emit(x) + return + +func remove_tab(tooltip : String) -> void: + for x : Control in buttons: + if x.get_src() == tooltip: + x.queue_free() + return + +func rename_tab(_tab_name : String, tooltip : String, new_tab_name : String, new_tooltip : String) -> void: + for x : Button in buttons: + if x.get_src() == tooltip: + x.set_src(new_tooltip) + x.set_text(new_tab_name) + return + +func set_select_color(color : Color) -> void: + _select_color = color.lightened(0.4) + +func set_ref(tab : TabBar) -> void: + _reference = tab + update() + +func set_enable(e : bool) -> void: + _enable_update = e + visible = e + if e: + _updating = false + update() + return + for x : Variant in hbox: + if is_instance_valid(x): + x.queue_free() + for x : Variant in buttons: + if is_instance_valid(x): + x.queue_free() + buttons.clear() + hbox.clear() + +func _on_pin(btn : Object) -> void: + if btn: + if btn.has_method(&"get_src"): + var value : Variant = btn.call(&"get_src") + if value is String: + if value.is_empty(): + return + var x : int = pins.find(value) + if x > -1: + pins.remove_at(x) + else: + pins.append(value) + + if pins.size() > 30: + var exist : Dictionary[String, bool] = {} + for b : Button in buttons: + exist[b.tooltip_text] = true + + for y : int in range(pins.size() - 1, -1, -1): + if !exist.has(pins[y]): + pins.remove_at(y) + _on_rect_change() + update() + +func update(fllbck : bool = true) -> void: + if !_enable_update: + return + if _updating: + return + _updating = true + var tab : TabBar = _reference + if !is_instance_valid(tab): + set_deferred(&"_updating", false) + return + + for x : int in range(buttons.size() -1, -1, -1): + var _container : Variant = buttons[x] + if is_instance_valid(_container): + continue + buttons.remove_at(x) + + while buttons.size() < tab.tab_count: + var btn : Control = TAB.instantiate() + var control : Control = btn.get_button() + var cls : Button = btn.get_button_close() + + if style: + btn.set(&"theme_override_styles/panel", style) + if !control.gui_input.is_connected(_on_gui_pressed): + control.gui_input.connect(_on_gui_pressed.bind(control)) + if !control.pressed.is_connected(_on_pressed): + control.pressed.connect(_on_pressed.bind(control)) + if !cls.pressed.is_connected(_on_close): + cls.pressed.connect(_on_close.bind(control)) + if !btn.on_pin.is_connected(_on_pin): + btn.on_pin.connect(_on_pin) + buttons.append(btn) + + while buttons.size() > tab.tab_count: + var btn : Variant = buttons.pop_back() + if is_instance_valid(btn): + if btn is Node: + btn.queue_free() + else: + btn.free() + + if pins.size() > 0: + var indx : int = 0 + var control : Node = tab.get_parent_control() + if control: + for x : int in range(control.get_child_count()): + if x > -1 and tab.tab_count > x: + if pins.has(tab.get_tab_tooltip(x)): + if x != indx: + if x < control.get_child_count(): + control.move_child(control.get_child(x), indx) + indx += 1 + + var alpha_pin : Color = Color.WHITE + var errors : bool = false + alpha_pin.a = 0.25 + + for x : int in range(tab.tab_count): + var _container : Control = buttons[x] + var btn : Button = _container.get_button() + var pin : Button = _container.get_button_pin() + _container.visible = true + btn.tooltip_text = tab.get_tab_tooltip(x) + _container.set_text(tab.get_tab_title(x)) + btn.icon = tab.get_tab_icon(x) + + #if fllbck and (btn.tooltip_text.is_empty() or btn.text.begins_with("@VSplitContainer") or btn.text.begins_with("@VBoxContainer")): + #if btn.text.begins_with("@VSplitContainer") or btn.text.begins_with("@VBoxContainer"): + #btn.text = "File" + #errors = true + + if pin: + if pins.has(btn.tooltip_text): + _container.is_pinned = true + pin.set(&"theme_override_colors/icon_normal_color",_select_color) + elif _container.is_pinned: + _container.is_pinned = false + pin.set(&"theme_override_colors/icon_normal_color", alpha_pin) + + btn.set(&"theme_override_colors/icon_normal_color", Color.GRAY) + _container.color_rect.visible = false + _container.modulate.a = 0.85 + + if tab.current_tab > -1 and tab.current_tab < buttons.size(): + var _container : Control = buttons[tab.current_tab] + var btn : Button = _container.get_button() + + btn.set(&"theme_override_colors/icon_normal_color", _select_color) + _container.modulate.a = 1.0 + + var c : ColorRect = _container.color_rect + c.visible = true + c.color = _select_color + + + if _behaviour_collapsed < MAX_COLLAPSED: + var iminor : int = tab.current_tab - _behaviour_collapsed + var isup : int = tab.current_tab + _behaviour_collapsed + for x : int in range(tab.tab_count): + if x < iminor or x > isup: + var _btn : Control = buttons[x] + _btn.visible = false + + _on_rect_change() + + if fllbck and errors: + Engine.get_main_loop().create_timer(3.0).timeout.connect(update.bind(false)) + + set_deferred(&"_updating", false) + +func _on_close(btn : Button) -> void: + if is_instance_valid(_reference): + for x : int in range(0, _reference.tab_count, 1): + if _reference.get_tab_tooltip(x) == btn.tooltip_text: + _reference.tab_close_pressed.emit(x) + break + +func _on_gui(event : InputEvent) -> void: + if event is InputEventMouseButton: + if event.pressed: + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + if _behaviour_collapsed < MAX_COLLAPSED: + _behaviour_collapsed += 1 + update() + get_viewport().set_input_as_handled() + elif event.button_index == MOUSE_BUTTON_WHEEL_UP: + if _behaviour_collapsed > 0: + _behaviour_collapsed -= 1 + update() + get_viewport().set_input_as_handled() + +func _ready() -> void: + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical = Control.SIZE_FILL + + item_rect_changed.connect(_on_rect_change) + + if !gui_input.is_connected(_on_gui): + gui_input.connect(_on_gui) + + var bd : Control = EditorInterface.get_base_control() + if bd: + style = bd.get_theme_stylebox("panel", "") + if is_instance_valid(style): + style = style.duplicate() + if style is StyleBoxFlat: + style.border_width_top = 0.0 + style.border_width_left = 0.0 + style.border_width_right = 0.0 + style.border_width_bottom = 0.0 + style.expand_margin_left = 2.0 + style.content_margin_bottom = 0.0 + style.content_margin_top = 0.0 + style.content_margin_left = 0.0 + style.content_margin_right = 0.0 + + +func _on_rect_change() -> void: + if !_enable_update: + return + _dlt = TIME_WAIT - 0.005 + _try = 0 + set_physics_process(true) + +func get_reference() -> TabBar: + return _reference + +func _physics_process(delta: float) -> void: + _dlt += delta + if _dlt < TIME_WAIT: + return + _dlt = 0.0 + + var rsize : Vector2 = get_parent().get_parent().size + if rsize.x > 10.0: + for x : Node in container.get_children(): + container.remove_child(x) + + for x : Control in buttons: + var p : Node = x.get_parent() + if p: + p.remove_child(x) + + var current : HBoxContainer = null + + var index : int = 0 + + var min_size : float = 0.0 + var btn_size : float = 0.0 + for x : Control in buttons: + var bsize : float = x.get_rect().size.x + if current == null or (bsize > 0.0 and rsize.x < current.get_minimum_size().x + bsize + 12): + if hbox.size() > index: + current = hbox[index] + else: + current = HBoxContainer.new() + current.set(&"theme_override_constants/separation", 4) + hbox.append(current) + index += 1 + container.add_child(current) + current.add_child(x) + btn_size = maxf(btn_size, x.size.y) + if current: + var indx : int = current.get_index() + 1 + min_size = indx * (btn_size) #+ 12.5 + + if custom_minimum_size.y != min_size: + _try = 0 + set_physics_process(true) + custom_minimum_size.y = min_size + return + + _try += 1 + if _try % 5 == 0: + set_physics_process(false) diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/container.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/container.gd.uid new file mode 100644 index 00000000..151c97a0 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/container.gd.uid @@ -0,0 +1 @@ +uid://cnubduf2plac4 diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/container.tscn b/addons/script_splitter/core/ui/multi_split_container/taby/container.tscn new file mode 100644 index 00000000..74d44208 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/container.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=2 format=3 uid="uid://c86tj28gq8d6t"] + +[ext_resource type="Script" uid="uid://cnubduf2plac4" path="res://addons/script_splitter/core/ui/multi_split_container/taby/container.gd" id="1_fdccq"] + +[node name="Container" type="PanelContainer" node_paths=PackedStringArray("container")] +z_index = 10 +anchors_preset = 10 +anchor_right = 1.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +script = ExtResource("1_fdccq") +container = NodePath("Container") + +[node name="Container" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 1 diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd b/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd new file mode 100644 index 00000000..334a002e --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd @@ -0,0 +1,107 @@ +@tool +extends Control +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +signal on_pin(button : Object) + +@export var color_rect : ColorRect +@export var button_main : Button +@export var button_close : Button +@export var button_pin : Button +@export var changes : Label + +var is_pinned : bool = false + +func _ready() -> void: + add_to_group(&"SP_TAB_BUTTON") + mouse_entered.connect(_on_enter) + mouse_exited.connect(_on_exit) + + var c : Color = Color.WHITE + c.a = 0.25 + button_close.set(&"theme_override_colors/icon_normal_color", c) + if !is_pinned: + button_pin.set(&"theme_override_colors/icon_normal_color", c) + _on_exit() + + var settings : EditorSettings = EditorInterface.get_editor_settings() + + if settings: + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + if settings.has_setting("plugin/script_splitter/editor/tabs/close_button_visible"): + button_close.visible = settings.get_setting("plugin/script_splitter/editor/tabs/close_button_visible") + else: + settings.set_setting("plugin/script_splitter/editor/tabs/close_button_visible", true) + + if settings.has_setting("plugin/script_splitter/editor/tabs/pin_button_visible"): + button_pin.visible = settings.get_setting("plugin/script_splitter/editor/tabs/pin_button_visible") + else: + settings.set_setting("plugin/script_splitter/editor/tabs/pin_button_visible", true) + +func _on_change() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings: + var st : PackedStringArray = settings.get_changed_settings() + if "plugin/script_splitter/editor/tabs/close_button_visible" in st: + button_close.visible = settings.get_setting("plugin/script_splitter/editor/tabs/close_button_visible") + if "plugin/script_splitter/editor/tabs/pin_button_visible" in st: + button_pin.visible = settings.get_setting("plugin/script_splitter/editor/tabs/pin_button_visible") + +func _on_enter() -> void: + add_to_group(&"__SPLITER_BUTTON_TAB__") + +func _on_exit() -> void: + remove_from_group(&"__SPLITER_BUTTON_TAB__") + + +func get_reference() -> TabBar: + return get_parent().get_parent().get_parent().get_reference() + +func get_button_pin() -> Button: + return button_pin + +func _on_pin_pressed() -> void: + on_pin.emit(self) + +func set_close_visible(e : bool) -> void: + button_close.visible = e + +func set_src(src : String) -> void: + button_main.tooltip_text = src + +func get_src() -> String: + return button_main.tooltip_text + +func set_text(txt : String) -> void: + if txt.ends_with("(*)"): + button_main.text = txt.trim_suffix("(*)") + changes.modulate.a = 1.0 + return + button_main.text = txt + changes.modulate.a = 0.0 + +func get_button() -> Button: + return button_main + +func get_button_close() -> Button: + return button_close + +func _get_drag_data(__ : Vector2) -> Variant: + return button_main._get_drag_data(__) + +func _drop_data(_at_position: Vector2, data: Variant) -> void: + button_main._drop_data(_at_position, data) + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + return button_main._can_drop_data(at_position, data) + +func get_selected_color() -> Color: + return color_rect.color diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd.uid new file mode 100644 index 00000000..7b8e1de1 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd.uid @@ -0,0 +1 @@ +uid://c3hg84uuly33f diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd b/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd new file mode 100644 index 00000000..7ea765d7 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd @@ -0,0 +1,60 @@ +@tool +extends VSeparator +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +var _delta : float = 0.0 +var _ref : Control = null + +func _ready() -> void: + visible = false + z_index = RenderingServer.CANVAS_ITEM_Z_MAX - 1 + z_as_relative = false + mouse_filter = Control.MOUSE_FILTER_IGNORE + set_process(_ref != null) + +func update(ref : Control) -> void: + _ref = ref + _delta = 0.0 + visible = _ref != null + set_process(visible) + +func delete() -> void: + _delta = 10.0 + _ref = null + queue_free() + +func _process(delta: float) -> void: + _delta += delta + if _delta < 0.5: + return + if is_instance_valid(_ref) and is_inside_tree(): + if _ref.get_global_rect().has_point(get_global_mouse_position()): + return + + if !is_queued_for_deletion(): + queue_free() + +func _get_drag_data(__ : Vector2) -> Variant: + if !_ref: + return null + return _ref.owner.button_main._get_drag_data(__) + +func _drop_data(_at_position: Vector2, data: Variant) -> void: + if _ref: + _ref.owner.button_main._drop_data(_at_position, data) + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + if !_ref: + return false + return _ref.owner.button_main._can_drop_data(at_position, data) + +func get_selected_color() -> Color: + if !_ref: + return Color.GRAY + return _ref.owner.get_selected_color() diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd.uid new file mode 100644 index 00000000..b0e41f3c --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/separator.gd.uid @@ -0,0 +1 @@ +uid://d05p8nj7f18s0 diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/separator.tscn b/addons/script_splitter/core/ui/multi_split_container/taby/separator.tscn new file mode 100644 index 00000000..4c31b0a7 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/separator.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://dwe7jhlq2p40h"] + +[ext_resource type="Script" uid="uid://d05p8nj7f18s0" path="res://addons/script_splitter/core/ui/multi_split_container/taby/separator.gd" id="1_byjdv"] + +[sub_resource type="StyleBoxLine" id="StyleBoxLine_ksadu"] +color = Color(0.62352943, 0.77176476, 0.7764706, 1) +grow_begin = 4.0 +grow_end = 4.0 +thickness = 24 + +[node name="HSeparator" type="VSeparator"] +visible = false +z_index = 4095 +z_as_relative = false +offset_right = 4.0 +offset_bottom = 4.0 +mouse_filter = 2 +theme_override_styles/separator = SubResource("StyleBoxLine_ksadu") +script = ExtResource("1_byjdv") diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd b/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd new file mode 100644 index 00000000..94cc300a --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd @@ -0,0 +1,226 @@ +@tool +extends Button +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const SEPARATOR = preload("./../../../../core/ui/multi_split_container/taby/separator.tscn") +const DRAG_TIME : float = 0.15 +static var line : VSeparator = null + + +var _delta : float = 0.0 +var _last_control : Control = null + +var is_drag : bool = false: + set(e): + is_drag = e + if is_drag: + on_drag() + Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) + else: + out_drag() + if Input.mouse_mode != Input.MOUSE_MODE_VISIBLE: + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + +var _fms : float = 0.0 + +func _ready() -> void: + auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED + set_process(false) + add_to_group(&"SP_TAB_BUTTON") + setup() + +func on_drag() -> void: + var tab : TabBar = owner.get_reference() + if tab: + for x : Node in tab.get_tree().get_nodes_in_group(&"ScriptSplitter"): + if x.has_method(&"dragged"): + x.call(&"dragged", tab, true) + +func out_drag() -> void: + var tab : TabBar = owner.get_reference() + if tab: + for x : Node in tab.get_tree().get_nodes_in_group(&"ScriptSplitter"): + if x.has_method(&"dragged"): + x.call(&"dragged", tab, false) + +func _get_drag_data(__ : Vector2) -> Variant: + if !button_pressed: + pressed.emit() + + var c : Control = duplicate(0) + c.mouse_filter = Control.MOUSE_FILTER_IGNORE + c.z_index = RenderingServer.CANVAS_ITEM_Z_MAX - 2 + set_drag_preview(c) + + return self + +func _drop_data(_at_position: Vector2, data: Variant) -> void: + if is_instance_valid(line): + line.delete() + if data is Node: + if data == self: + return + elif data.is_in_group(&"SP_TAB_BUTTON"): + if is_instance_valid(line): + line.update(self) + var node : Node = owner + if node: + var idx : int = node.get_index() + if idx >= 0: + var _node : Node = data.owner + var lft : bool = false + if owner.get_global_mouse_position().x <= owner.get_global_rect().get_center().x: + lft = true + var root : Node = _node + for __ : int in range(3): + root = root.get_parent() + if !is_instance_valid(root): + out_drag() + return + + for x : Node in get_tree().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + if x.has_method(&"get_builder"): + var o : Object = x.call(&"get_builder") + if o.has_method(&"swap_by_src"): + o.call(&"swap_by_src", data.tooltip_text, tooltip_text, lft) + break + if root: + if root.has_method(&"update"): + root.call(&"update") + + out_drag() + +func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: + if data is Node: + if data == self: + return false + elif data.is_in_group(&"SP_TAB_BUTTON"): + _last_control = data + _delta = 0.0 + if !is_instance_valid(line): + line = SEPARATOR.instantiate() + var root : Node = Engine.get_main_loop().root + if root: + root.add_child(line) + if line: + var rct : Rect2 = owner.get_global_rect() + line.update(self) + if owner.get_global_mouse_position().x <= owner.get_global_rect().get_center().x: + line.global_position = rct.position + else: + line.global_position = Vector2(rct.end.x, rct.position.y) - Vector2(line.size.x * 1.5, 0.0) + + var style : StyleBoxLine = line.get(&"theme_override_styles/separator") + style.set(&"thickness",size.y) + style.set(&"color",data.get_selected_color()) + return true + return false + +func reset() -> void: + if is_drag: + set_process(false) + is_drag = false + if is_inside_tree(): + var parent : Node = self + + for __ : int in range(10): + parent = parent.get_parent() + if parent.has_signal(&"out_dragging"): + break + if !is_instance_valid(parent): + return + if parent.has_signal(&"out_dragging"): + for x : Node in parent.get_children(): + if x is TabContainer: + parent.emit_signal(&"out_dragging",x.get_tab_bar()) + return + +func _enter_tree() -> void: + if !is_in_group(&"__SPLITER_TAB__"): + add_to_group(&"__SPLITER_TAB__") + if is_node_ready(): + return + owner.modulate.a = 0.0 + get_tree().create_tween().tween_property(owner, "modulate:a", 1.0, 0.5) + +func _exit_tree() -> void: + if is_in_group(&"__SPLITER_TAB__"): + remove_from_group(&"__SPLITER_TAB__") + +func _process(delta: float) -> void: + _fms += delta + if _fms > DRAG_TIME: + if is_drag: + if !Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + set_process(false) + is_drag = false + var parent : Node = self + + for __ : int in range(10): + parent = parent.get_parent() + if parent.has_signal(&"out_dragging"): + break + if !is_instance_valid(parent): + return + if parent.has_signal(&"out_dragging"): + for x : Node in parent.get_children(): + if x is TabContainer: + parent.emit_signal(&"out_dragging",x.get_tab_bar()) + return + else: + if !Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + set_process(false) + return + is_drag = true + var parent : Node = self + for __ : int in range(10): + parent = parent.get_parent() + if parent.has_signal(&"on_dragging"): + break + if !is_instance_valid(parent): + return + if parent.has_signal(&"on_dragging"): + for x : Node in parent.get_children(): + if x is TabContainer: + parent.emit_signal(&"on_dragging",x.get_tab_bar()) + return + +func setup() -> void: + if !gui_input.is_connected(_on_input): + gui_input.connect(_on_input) + if !is_in_group(&"__SPLITER_TAB__"): + add_to_group(&"__SPLITER_TAB__") + +func _on_input(e : InputEvent) -> void: + if e is InputEventMouseButton: + if e.button_index == MOUSE_BUTTON_LEFT: + is_drag = false + if e.pressed: + _fms = 0.0 + set_process(true) + else: + set_process(false) + if _fms >= DRAG_TIME: + var parent : Node = self + for __ : int in range(10): + parent = parent.get_parent() + if parent.has_signal(&"out_dragging"): + break + if !is_instance_valid(parent): + return + if parent.has_signal(&"out_dragging"): + for x : Node in parent.get_children(): + if x is TabContainer: + parent.emit_signal(&"out_dragging",x.get_tab_bar()) + return + #elif e.button_index == MOUSE_BUTTON_RIGHT: + #pressed.emit() + +func get_selected_color() -> Color: + return owner.get_selected_color() diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd.uid new file mode 100644 index 00000000..b7e4df5a --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/tab.gd.uid @@ -0,0 +1 @@ +uid://cf447gbwtnwsl diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/tab.tscn b/addons/script_splitter/core/ui/multi_split_container/taby/tab.tscn new file mode 100644 index 00000000..b37bf896 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/tab.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=8 format=3 uid="uid://bei2ybryufpnd"] + +[ext_resource type="Script" uid="uid://c3hg84uuly33f" path="res://addons/script_splitter/core/ui/multi_split_container/taby/container_button.gd" id="1_c6ml5"] +[ext_resource type="Script" uid="uid://cf447gbwtnwsl" path="res://addons/script_splitter/core/ui/multi_split_container/taby/tab.gd" id="2_gel5x"] +[ext_resource type="Script" uid="uid://bkj4lec06udg5" path="res://addons/script_splitter/core/ui/multi_split_container/taby/changes.gd" id="3_f5jk5"] +[ext_resource type="Texture2D" uid="uid://7rel5pr2g7d2" path="res://addons/script_splitter/assets/pin.svg" id="3_y1lda"] +[ext_resource type="Texture2D" uid="uid://4juherhkw8hp" path="res://addons/script_splitter/assets/Close.svg" id="4_b84sh"] +[ext_resource type="Script" uid="uid://bi84jgnpiilbi" path="res://addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd" id="5_4jcjw"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hvct0"] + +[node name="PanelContainer" type="PanelContainer" node_paths=PackedStringArray("color_rect", "button_main", "button_close", "button_pin", "changes")] +offset_right = 49.0 +offset_bottom = 33.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_hvct0") +script = ExtResource("1_c6ml5") +color_rect = NodePath("VBoxContainer/Selected") +button_main = NodePath("VBoxContainer/ButtonContainer/PanelContainer") +button_close = NodePath("VBoxContainer/ButtonContainer/Close") +button_pin = NodePath("VBoxContainer/ButtonContainer/Pin") +changes = NodePath("VBoxContainer/ButtonContainer/Changes") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="Selected" type="ColorRect" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 2) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +mouse_filter = 1 +color = Color(0.4904182, 0.5339965, 0.61462164, 1) + +[node name="ButtonContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_constants/separation = 0 + +[node name="PanelContainer" type="Button" parent="VBoxContainer/ButtonContainer"] +auto_translate_mode = 2 +layout_mode = 2 +focus_mode = 0 +theme_override_colors/font_focus_color = Color(1, 1, 1, 1) +theme_override_colors/font_pressed_color = Color(1, 1, 1, 1) +theme_override_colors/icon_normal_color = Color(1, 1, 1, 1) +button_mask = 3 +shortcut_feedback = false +shortcut_in_tooltip = false +flat = true +script = ExtResource("2_gel5x") + +[node name="Changes" type="Label" parent="VBoxContainer/ButtonContainer"] +layout_mode = 2 +mouse_filter = 1 +text = "*" +script = ExtResource("3_f5jk5") + +[node name="Pin" type="Button" parent="VBoxContainer/ButtonContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_colors/icon_normal_color = Color(1, 1, 1, 0.25) +shortcut_feedback = false +shortcut_in_tooltip = false +icon = ExtResource("3_y1lda") +flat = true +icon_alignment = 1 +script = ExtResource("5_4jcjw") + +[node name="Close" type="Button" parent="VBoxContainer/ButtonContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_colors/font_color = Color(1, 1, 1, 0.25) +theme_override_colors/icon_normal_color = Color(1, 1, 1, 0.25) +shortcut_feedback = false +shortcut_in_tooltip = false +icon = ExtResource("4_b84sh") +flat = true +script = ExtResource("5_4jcjw") + +[connection signal="pressed" from="VBoxContainer/ButtonContainer/Pin" to="." method="_on_pin_pressed"] diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd b/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd new file mode 100644 index 00000000..eb188cc6 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd @@ -0,0 +1,17 @@ +@tool +extends Control + +func _ready() -> void: + add_to_group(&"SP_TAB_BUTTON") + +func _get_drag_data(__ : Vector2) -> Variant: + return owner.button_main._get_drag_data(__) + +func _drop_data(_at_position: Vector2, data: Variant) -> void: + owner.button_main._drop_data(_at_position, data) + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + return owner.button_main._can_drop_data(at_position, data) + +func get_selected_color() -> Color: + return owner.get_selected_color() diff --git a/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd.uid b/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd.uid new file mode 100644 index 00000000..428afec6 --- /dev/null +++ b/addons/script_splitter/core/ui/multi_split_container/taby/tab_dd.gd.uid @@ -0,0 +1 @@ +uid://bi84jgnpiilbi diff --git a/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd b/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd new file mode 100644 index 00000000..23f8130e --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd @@ -0,0 +1,41 @@ +@tool +extends CodeEdit +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4f +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +const UPDATE_TIME : float = 0.25 + +var _dlt : float = 0.0 +var _text : String = "" +var _dlt_update : float = UPDATE_TIME + +func set_text_reference(txt : String) -> void: + if _text == txt: + return + _text = txt + _dlt = 0.0 + _dlt_update = UPDATE_TIME + (txt.length() * 0.00001) + + set_process(true) + +func _init() -> void: + if is_node_ready(): + _ready() + +func _ready() -> void: + set_process(false) + +func _process(delta: float) -> void: + _dlt += delta + if _dlt > _dlt_update: + set_process(false) + var sv : float = scroll_vertical + var sh : int = scroll_horizontal + text = _text + scroll_vertical = sv + scroll_horizontal = sh diff --git a/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd.uid b/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd.uid new file mode 100644 index 00000000..3e845289 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/editor/ssp_editor.gd.uid @@ -0,0 +1 @@ +uid://bec3qea1dh8xe diff --git a/addons/script_splitter/core/ui/splitter/editor_container.gd b/addons/script_splitter/core/ui/splitter/editor_container.gd new file mode 100644 index 00000000..05fe7051 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/editor_container.gd @@ -0,0 +1,110 @@ +@tool +extends TabContainer +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const Dottab = preload("./../../../core/ui/splitter/taby/dottab.gd") +const CLOSE = preload("./../../../assets/Close.svg") + +const GLOBALS : PackedStringArray = ["_GlobalScope", "_GDScript"] + +# +signal focus(o : TabContainer, index : int) +signal remove(o : TabContainer, index : int) + +var _new_tab_settings : bool = false +var _tab_queue : int = -1 +var _last_selected : int = -1 + +func _enter_tree() -> void: + add_to_group(&"__SC_SPLITTER__") + +func _exit_tree() -> void: + if is_in_group(&"__SC_SPLITTER__"): + remove_from_group(&"__SC_SPLITTER__") + +func _ready() -> void: + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical = Control.SIZE_EXPAND_FILL + + + auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED + + var tb : TabBar = get_tab_bar() + if tb: + tb.auto_translate_mode = auto_translate_mode + + + drag_to_rearrange_enabled = true + + #CONNECT + var tab : TabBar = get_tab_bar() + tab.set_script(Dottab) + tab.tab_selected.connect(_on_selected) + tab.active_tab_rearranged.connect(_on_rearranged) + + tab.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ACTIVE_ONLY + tab.tab_close_pressed.connect(_on_remove) + tab.select_with_rmb = true + tab.on_start_drag.connect(on_drag) + tab.on_stop_drag.connect(out_drag) + +func _on_rearranged(t : int) -> void: + if _last_selected == t or t < 0: + return + + for x : int in [_last_selected, t]: + if x < 0 or x >= get_tab_count(): + return + + var sc : SceneTree = Engine.get_main_loop() + if sc: + for x : Node in sc.get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + x.call(&"move_item_container", self, _last_selected, t) + +func _set_tab() -> void: + if current_tab != _tab_queue and _tab_queue > -1 and _tab_queue < get_tab_count(): + current_tab = _tab_queue + _new_tab_settings = false + +func set_tab(index : int) -> void: + if index > -1 and index < get_tab_count(): + _tab_queue = index + + if _new_tab_settings: + return + + _new_tab_settings = true + _set_tab.call_deferred() + +func on_drag(tab : TabBar) -> void: + for x : Node in tab.get_tree().get_nodes_in_group(&"ScriptSplitter"): + if x.has_method(&"dragged"): + x.call(&"dragged", tab, true) + +func out_drag(tab : TabBar) -> void: + for x : Node in tab.get_tree().get_nodes_in_group(&"ScriptSplitter"): + if x.has_method(&"dragged"): + x.call(&"dragged", tab, false) + +func _on_remove(index : int) -> void: + remove.emit(self, index) + +func _on_selected(value : int) -> void: + _last_selected = value + focus.emit(self, value) + +func get_root() -> Node: + return self + +func set_item_tooltip(idx : int, txt : String) -> void: + if idx > -1 and get_tab_count() > idx: + set_tab_tooltip(idx, txt) + +func set_item_text(idx : int, txt : String) -> void: + if idx > -1 and get_tab_count() > idx: + set_tab_title(idx, txt) diff --git a/addons/script_splitter/core/ui/splitter/editor_container.gd.uid b/addons/script_splitter/core/ui/splitter/editor_container.gd.uid new file mode 100644 index 00000000..047e001b --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/editor_container.gd.uid @@ -0,0 +1 @@ +uid://b45owls32pkxk diff --git a/addons/script_splitter/core/ui/splitter/io/io_bar.gd b/addons/script_splitter/core/ui/splitter/io/io_bar.gd new file mode 100644 index 00000000..becae8cd --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/io/io_bar.gd @@ -0,0 +1,122 @@ +@tool +extends ScrollContainer +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const PIN = preload("./../../../../assets/pin.svg") +const FILL_EXPAND = preload("./../../../../assets/fill_expand.svg") +const SPLIT_CPLUS_TOOL = preload("./../../../../assets/split_cplus_tool.svg") +const SPLIT_MINUS_TOOL = preload("./../../../../assets/split_minus_tool.svg") +const SPLIT_PLUS_TOOL = preload("./../../../../assets/split_plus_tool.svg") +const SPLIT_RMINUS_TOOL = preload("./../../../../assets/split_rminus_tool.svg") +const SPLIT_RPLUS_TOOL = preload("./../../../../assets/split_rplus_tool.svg") +const SPLIT_CMINUS_TOOL = preload("./../../../../assets/split_cminus_tool.svg") +const ATOP = preload("./../../../../assets/atop.png") + + +const PAD : float = 12.0 + +#CFG +var enable_expand : bool = true +var enable_horizontal_split : bool = true +var enable_vertical_split : bool = true +var enable_pop_script : bool = true +var enable_sub_split : bool = true + +var _root : VBoxContainer = null +var _min_size : float = 0.0 + +@warning_ignore("unused_private_class_variable") +var _pin_root : Control = null + +func _ready() -> void: + if _root == null: + _root = VBoxContainer.new() + _root.alignment = BoxContainer.ALIGNMENT_BEGIN + _root.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _root.size_flags_vertical = Control.SIZE_EXPAND_FILL + add_child(_root) + clear() + _setup() + + custom_minimum_size.x = _min_size + PAD + horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + vertical_scroll_mode = ScrollContainer.SCROLL_MODE_SHOW_NEVER + +func get_root() -> Node: + return _root + +func _enter_tree() -> void: + add_to_group(&"__script_splitter__IO__") + +func _exit_tree() -> void: + remove_from_group(&"__script_splitter__IO__") + +# Traduction? +func _tr(st : String) -> String: + # ... + return st.capitalize() + +func clear() -> void: + if _root: + if is_inside_tree(): + for x : Node in _root.get_children(): + x.queue_free() + else: + for x : Node in _root.get_children(): + x.free() + + +func setup() -> void: + clear() + _setup() + +func _setup() -> void: + if !_root: + return + if enable_expand: + make_function(&"EXPAND", FILL_EXPAND, _tr("Expand/Unexpand current tab container")) + if enable_horizontal_split: + make_function(&"SPLIT_COLUMN", SPLIT_CPLUS_TOOL, _tr("Split to new column")) + make_function(&"MERGE_COLUMN", SPLIT_CMINUS_TOOL, _tr("Merge current column")) + if enable_vertical_split: + make_function(&"SPLIT_ROW", SPLIT_RPLUS_TOOL, _tr("Split to new row")) + make_function(&"MERGE_ROW", SPLIT_RMINUS_TOOL, _tr("Merge current row")) + if enable_sub_split: + make_function(&"SPLIT_SUB", SPLIT_PLUS_TOOL, _tr("Sub Split current editor")) + make_function(&"MERGE_SPLIT_SUB", SPLIT_MINUS_TOOL, _tr("Merge sub split of current editor")) + if enable_pop_script: + make_function(&"MAKE_FLOATING", ATOP, _tr("Make separate window")) + +func enable(id : StringName, e : bool) -> void: + for x : Node in _root.get_children(): + if x.name == id: + x.set(&"disabled", !e) + +func get_button(id : String) -> Button: + if _root.has_node(id): + var node : Node = _root.get_node(id) + if node is Button: + return node + return null + +func make_function(id : StringName, icon : Texture2D = null, txt : String = "") -> void: + var btn : Button = Button.new() + btn.name = id + + btn.pressed.connect(_call.bind(id)) + btn.icon = icon + btn.alignment = HORIZONTAL_ALIGNMENT_CENTER + btn.tooltip_text = txt + btn.flat = is_instance_valid(icon) + _min_size = maxf(icon.get_size().x, _min_size) + _root.add_child(btn) + +func _call(id : StringName) -> void: + for x : Node in get_tree().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + if x.has_method(&"_io_call"): + x.call(&"_io_call", id) diff --git a/addons/script_splitter/core/ui/splitter/io/io_bar.gd.uid b/addons/script_splitter/core/ui/splitter/io/io_bar.gd.uid new file mode 100644 index 00000000..3d47ef51 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/io/io_bar.gd.uid @@ -0,0 +1 @@ +uid://b817wwia7hrqd diff --git a/addons/script_splitter/core/ui/splitter/splitter_container.gd b/addons/script_splitter/core/ui/splitter/splitter_container.gd new file mode 100644 index 00000000..cb916871 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_container.gd @@ -0,0 +1,307 @@ +@tool +extends MarginContainer +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const SplitterRoot = preload("./../../../core/ui/splitter/splitter_root.gd") +const HandlerContainer = preload("./../../../core/base/container.gd") +const BaseContainerItem = preload("splitter_item.gd") + +const SplitterEditorContainer = preload("./../../../core/ui/splitter/splitter_editor_container.gd") + +const Overlay = preload("./../../../core/ui/splitter/taby/overlay.gd") +const CODE_NAME_TWISTER = preload("./../../../assets/github_CodeNameTwister.svg") + +var _handler_container : HandlerContainer = null +var _base_container : TabContainer = null + +var _root : Container = null +var _root_container : SplitterRoot = null + +var _last_editor_container : SplitterEditorContainer.Editor = null + +var _overlay : Overlay = null + +var swap_by_button : bool = true + +func get_root() -> SplitterRoot: + return _root_container + +func get_base_editors() -> Array[Node]: + if is_instance_valid(_base_container): + return _base_container.get_children() + return [] + +func initialize(container : TabContainer, handler_container : HandlerContainer) -> void: + _setup() + + _handler_container = handler_container + _base_container = container + + _root = self + + var credits : TextureRect = TextureRect.new() + add_child(credits) + + credits.texture = CODE_NAME_TWISTER + credits.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + credits.size_flags_vertical = Control.SIZE_SHRINK_CENTER + credits.expand_mode = TextureRect.EXPAND_KEEP_SIZE + credits.stretch_mode = TextureRect.STRETCH_KEEP_CENTERED + credits.modulate.a = 0.25 + + if is_instance_valid(_base_container): + var root : Node = _base_container.get_parent() + root.add_child(_root) + root.move_child(_root, mini(_base_container.get_index(), 0)) + + #_root.add_child(_root_container) + + _root.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _root.size_flags_vertical = Control.SIZE_EXPAND_FILL + + var vspl : HBoxContainer = HBoxContainer.new() + + _root.add_child(vspl) + vspl.size_flags_horizontal = Control.SIZE_EXPAND_FILL + vspl.size_flags_vertical = Control.SIZE_EXPAND_FILL + + var base : SplitterRoot = create_base_container(vspl, false) + base.max_columns = 1 + _root_container = base + + var io : Node = _handler_container.get_io_bar() + if io.get_parent() == null: + vspl.add_child(io) + else: + io = HandlerContainer.IoBar.new() + io.enable_vertical_split = false + vspl.add_child(io) + + initialize_editor_contianer() + + _overlay = Overlay.new() + add_child(_overlay) + +func _on_change() -> void: + var dt : Array = ["plugin/script_splitter/editor/behaviour/swap_by_double_click_separator_button"] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["swap_by_button", "plugin/script_splitter/editor/behaviour/swap_by_double_click_separator_button"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + +func initialize_editor_contianer() -> void: + if _root_container.get_child_count() > 0: + for x : Node in _root_container.get_children(): + x.queue_free() + _last_editor_container = create_new_editor_container(_root_container, true) + +func swap(value : Variant) -> void: + if !swap_by_button: + return + + if !is_instance_valid(value): + return + + elif !is_instance_valid(_root_container) or _root_container.get_child_count() == 0: + return + + elif !value is SplitterRoot.LineSep: + return + + var caller : SplitterRoot.LineSep = value + + var _main : SplitterRoot = caller.get_parent() + + if !is_instance_valid(_main): + return + + var separators : Array = _main.get_separators() + if separators.size() == 0: + return + + var index : int = 0 + var linesep : Object = null + for x : Object in separators: + if x == caller: + linesep =x + break + index += 1 + + if linesep: + if linesep.is_vertical: + var atotal : int = 1 + var btotal : int = 1 + var nodes : Array[Node] = [] + + for x : int in range(index + 1, separators.size(), 1): + var clinesep : Object = separators[x] + if clinesep.is_vertical: + break + atotal += 1 + for x : int in range(index - 1, -1, -1): + var clinesep : Object = separators[x] + if clinesep.is_vertical: + break + btotal += 1 + + var cindex : int = index + while atotal > 0: + cindex += 1 + atotal -= 1 + if cindex < _main.get_child_count(): + nodes.append(_main.get_child(cindex)) + continue + break + + for x : Node in nodes: + cindex = btotal + while cindex > 0: + cindex -= 1 + var idx : int = x.get_index() - 1 + if _main.get_child_count() > idx: + _main.move_child(x, idx) + else: + index += 1 + if _main.get_child_count() > index: + var child : Node = _main.get_child(index - 1) + _main.move_child(child, index) + +func _enter_tree() -> void: + add_to_group(&"ScriptSplitter") + +func _exit_tree() -> void: + remove_from_group(&"ScriptSplitter") + + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + + +func dragged(tab : TabBar, is_drag : bool) -> void: + if is_instance_valid(_overlay): + if is_drag: + _overlay.start(tab) + else: + if _overlay.stop(tab): + var container : Node = _overlay.get_container() + var from : Container = tab.get_parent() + if is_instance_valid(container) and is_instance_valid(from): + if from != container: + _handler_container.swap_tab.emit(from, tab.current_tab, container) + else: + var type : StringName = _overlay.get_type_split() + if !type.is_empty(): + _handler_container.same_swap_tab.emit(from, tab.current_tab, type) + +func create_new_column() -> SplitterEditorContainer.Editor: + var item : BaseContainerItem = get_base_container_item(_last_editor_container) + var root : Container = get_base_container(_last_editor_container) + var index : int = item.get_index() + var custom_position : bool = index >= 0 and index < item.get_parent().get_child_count() - 1 + _last_editor_container = create_editor_container(create_base_container_item(root)) + if custom_position: + root.move_child(get_base_container_item(_last_editor_container), index + 1) + return _last_editor_container + +func create_new_row() -> SplitterEditorContainer.Editor: + var root : Container = get_base_container(_last_editor_container) + var index : int = root.get_index() + var custom_position : bool = index >= 0 and index < root.get_parent().get_child_count() - 1 + _last_editor_container = create_new_editor_container(_root_container)# create_editor_container(create_base_container_item(create_base_container(_root_container))) + if custom_position: + _root_container.move_child(get_base_container(_last_editor_container).get_parent(), index + 1) + return _last_editor_container + + +func set_current_editor(container : Node) -> bool: + if container is SplitterEditorContainer.Editor: + _last_editor_container = container + return true + return false + +func get_base_container(editor : SplitterEditorContainer.Editor) -> Container: + return editor.get_node("./../../../") + +func get_base_container_item(editor : SplitterEditorContainer.Editor) -> BaseContainerItem: + return editor.get_node("./../../") + +func create_base_container(c_root : Node, _add_to_group : bool = true) -> Container: + var b_root : Container = SplitterRoot.new() + b_root.max_columns = 0 + c_root.add_child(b_root) + b_root.size_flags_horizontal = Control.SIZE_EXPAND_FILL + b_root.size_flags_vertical = Control.SIZE_EXPAND_FILL + + if _add_to_group: + b_root.add_to_group(&"__SP_BR__") + + return b_root + + +func create_base_container_item(c_root : Container) -> BaseContainerItem: + var b_item : BaseContainerItem = BaseContainerItem.new() + c_root.add_child(b_item) + + b_item.size_flags_horizontal = Control.SIZE_EXPAND_FILL + b_item.size_flags_vertical = Control.SIZE_EXPAND_FILL + + return b_item + +func create_editor_container(c_root : BaseContainerItem) -> SplitterEditorContainer.Editor: + var b_editor : SplitterEditorContainer = SplitterEditorContainer.new() + + c_root.add_child(b_editor) + b_editor.get_editor() + + var editor : SplitterEditorContainer.Editor = b_editor.get_editor() + + editor.focus.connect(_handler_container.on_focus) + editor.remove.connect(_handler_container.on_remove) + + editor.get_tab_bar().tab_rmb_clicked.connect(_on_rmb_clicked.bind(editor)) + return editor + +func _on_rmb_clicked(index : int, tab : Variant) -> void: + if tab is SplitterEditorContainer.Editor: + _handler_container.rmb_click.emit(index, tab) + +func create_new_editor_container(c_root : Node, _add_to_group : bool = true) -> SplitterEditorContainer.Editor: + return create_editor_container(create_base_container_item(create_base_container(c_root, _add_to_group))) + +func get_current_editor() -> SplitterEditorContainer.Editor: + return _last_editor_container + +func reset() -> void: + _root.queue_free() + if is_instance_valid(_base_container): + _base_container.visible = true + + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func notify_creation() -> void: + if is_instance_valid(_base_container) and _base_container.visible: + _base_container.visible = false diff --git a/addons/script_splitter/core/ui/splitter/splitter_container.gd.uid b/addons/script_splitter/core/ui/splitter/splitter_container.gd.uid new file mode 100644 index 00000000..377bda5e --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_container.gd.uid @@ -0,0 +1 @@ +uid://dyo7c2g4uwn0g diff --git a/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd b/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd new file mode 100644 index 00000000..62b221f3 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd @@ -0,0 +1,82 @@ +@tool +extends VBoxContainer +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const Editor = preload("./../../../core/ui/splitter/editor_container.gd") +const CONTAINER = preload("./../../../core/ui/multi_split_container/taby/container.tscn") + +var _editor : Editor = null +var _tab_old_behaviour : bool = false: + set = _on_behaviour_changed +var tab : Node = null + +func _on_behaviour_changed(e) -> void: + _tab_old_behaviour = e + if is_instance_valid(tab): + tab.set_enable(!_tab_old_behaviour) + _editor.tabs_visible = _tab_old_behaviour + +func _on_change() -> void: + var dt : Array = ["plugin/script_splitter/editor/tabs/use_old_behaviour"] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + break + +func _setup() -> void: + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["_tab_old_behaviour", "plugin/script_splitter/editor/tabs/use_old_behaviour"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + +func _ready() -> void: + _editor = Editor.new() + + var iscale : int = -8 + set(&"theme_override_constants/separation", iscale) + + tab = CONTAINER.instantiate() + tab.set_ref(_editor.get_tab_bar()) + tab.set_enable(!_tab_old_behaviour) + _editor.tabs_visible = _tab_old_behaviour + + add_child(tab) + add_child(_editor) + + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical = Control.SIZE_EXPAND_FILL + +func _enter_tree() -> void: + add_to_group(&"__SP_EC__") + _setup() + +func _exit_tree() -> void: + remove_from_group(&"__SP_EC__") + + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + +func get_editor() -> Editor: + return _editor + +func update() -> void: + if !is_instance_valid(tab): + return + tab.update() diff --git a/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd.uid b/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd.uid new file mode 100644 index 00000000..f094d34e --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_editor_container.gd.uid @@ -0,0 +1 @@ +uid://dlqgddtcsov1m diff --git a/addons/script_splitter/core/ui/splitter/splitter_item.gd b/addons/script_splitter/core/ui/splitter/splitter_item.gd new file mode 100644 index 00000000..373d94d9 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_item.gd @@ -0,0 +1,67 @@ +@tool +extends "./../../../core/ui/multi_split_container/split_container_item.gd" + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +var _fms : float = 0.0 + +func _ready() -> void: + super() + modulate = Color.DARK_GRAY + set_physics_process(modulate != Color.WHITE) + +func _physics_process(delta: float) -> void: + _fms += delta * 0.24 + if _fms >= 1.0: + _fms = 1.0 + set_physics_process(false) + modulate = lerp(modulate, Color.WHITE, _fms) + +func _enter_tree() -> void: + super() + + add_to_group(&"__SP_IC__") + var parent : Node = get_parent() + if parent.has_method(&"expand_splited_container"): + _on_child(self) + +func _exit_tree() -> void: + add_to_group(&"__SP_IC__") + +func _on_child(n : Node) -> void: + if n is Control: + var parent : Node = get_parent() + if !n.child_entered_tree.is_connected(_on_child): + n.child_entered_tree.connect(_on_child) + + if !n.child_exiting_tree.is_connected(_out_child): + n.child_exiting_tree.connect(_out_child) + + if n.focus_mode != Control.FOCUS_NONE: + if !n.focus_entered.is_connected(parent.expand_splited_container): + n.focus_entered.connect(parent.expand_splited_container.bind(self)) + + for x : Node in n.get_children(): + _on_child(x) + +func _out_child(n : Node) -> void: + if n is Control: + var parent : Node = get_parent() + if n.child_entered_tree.is_connected(_on_child): + n.child_entered_tree.disconnect(_on_child) + + if n.child_exiting_tree.is_connected(_out_child): + n.child_exiting_tree.disconnect(_out_child) + + if n.focus_mode != Control.FOCUS_NONE: + if n.focus_entered.is_connected(parent.expand_splited_container): + n.focus_entered.disconnect(parent.expand_splited_container) + for x : Node in n.get_children(): + _out_child(x) + diff --git a/addons/script_splitter/core/ui/splitter/splitter_item.gd.uid b/addons/script_splitter/core/ui/splitter/splitter_item.gd.uid new file mode 100644 index 00000000..f9aa33bd --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_item.gd.uid @@ -0,0 +1 @@ +uid://b2kiv55ed0aj diff --git a/addons/script_splitter/core/ui/splitter/splitter_list.gd b/addons/script_splitter/core/ui/splitter/splitter_list.gd new file mode 100644 index 00000000..28d13d39 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_list.gd @@ -0,0 +1,106 @@ +@tool +extends ItemList +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +signal move_item_by_index(from : int, to : int) + +var _ss: Callable +var _delta : int = 0 +var _list : ItemList = null +var _dragged_item_index: int = -1 + +func _ready() -> void: + set_process(false) + set_physics_process(false) + +func update() -> void: + _delta = 0 + set_physics_process(true) + +func set_list(item : ItemList) -> void: + _list = item + +func set_reference(scall : Callable) -> void: + _ss = scall + +func changes(list : ItemList) -> bool: + if list.item_count != item_count: + return true + + for x : int in list.item_count: + if is_selected(x) != is_selected(x) or \ + get_item_text(x) != list.get_item_text(x) or\ + get_item_icon(x) != list.get_item_icon(x) or \ + get_item_icon_modulate(x) != list.get_item_icon_modulate(x) or \ + get_item_tooltip(x) != list.get_item_tooltip(x): + return true + + return false + +func _physics_process(__ : float) -> void: + _delta += 1 + if _delta < 10: + return + set_physics_process(false) + if !_ss.is_valid(): + return + if !changes(_list): + return + _ss.call() + +func _get_drag_data(at_position: Vector2) -> Variant: + var item_index : int = get_item_at_position(at_position) + + if item_index != -1: + _dragged_item_index = item_index + + var drag_preview : HBoxContainer = HBoxContainer.new() + var icon : TextureRect = TextureRect.new() + var label : Label = Label.new() + + drag_preview.set(&"theme_override_constants/separation", 0) + icon.texture = get_item_icon(0) + icon.modulate = get_item_icon_modulate(0) + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + label.text = get_item_text(item_index) + drag_preview.add_child(icon) + drag_preview.add_child(label) + + set_drag_preview(drag_preview) + var tp : String = get_item_tooltip(item_index) + + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"SP_TAB_BUTTON"): + if x is Control: + if tp == x.tooltip_text and x.has_method(&"_on_input"): + var ip : InputEventMouseButton = InputEventMouseButton.new() + ip.button_index = MOUSE_BUTTON_LEFT + ip.pressed = true + x.call(&"_on_input", ip) + + return item_index + return null + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + if typeof(data) == TYPE_INT and data > -1 and data == _dragged_item_index: + var drop_index : int = get_item_at_position(at_position) + + return drop_index != -1 and drop_index != data + + return false + +func _drop_data(at_position: Vector2, data: Variant) -> void: + if typeof(data) != TYPE_INT or data < 0 or data != _dragged_item_index: + return + var from_index : int = data as int + var to_index : int = get_item_at_position(at_position) + + if from_index != -1 and to_index != -1: + move_item_by_index.emit(from_index, to_index) + + _dragged_item_index = -1 diff --git a/addons/script_splitter/core/ui/splitter/splitter_list.gd.uid b/addons/script_splitter/core/ui/splitter/splitter_list.gd.uid new file mode 100644 index 00000000..b4b29c99 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_list.gd.uid @@ -0,0 +1 @@ +uid://c12gyjf2qmasp diff --git a/addons/script_splitter/core/ui/splitter/splitter_root.gd b/addons/script_splitter/core/ui/splitter/splitter_root.gd new file mode 100644 index 00000000..3b4aeda4 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_root.gd @@ -0,0 +1,108 @@ +@tool +extends "./../../../core/ui/multi_split_container/multi_split_container.gd" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const EXPAND = preload("./../../../assets/expand.svg") + +var __setup : bool = false +var _delta : float = 0.0 + +func _init() -> void: + super() + + drag_button_icon = EXPAND + drag_button_size = 24.0 + behaviour_expand_on_focus = true + behaviour_can_expand_focus_same_container = false + behaviour_expand_smoothed = true + drag_button_always_visible = false + drag_button_modulate = Color.WHITE + behaviour_expand_on_double_click = true + behaviour_can_move_by_line = true + + _setup() + +func _ready() -> void: + super() + modulate.a = 0.0 + set_physics_process(true) + +func _physics_process(delta : float) -> void: + _delta += delta * 2.0 + if _delta >= 1.0: + _delta = 1.0 + set_physics_process(false) + modulate.a = _delta + + +func _on_change() -> void: + var dt : Array = ["plugin/script_splitter/editor/behaviour/expand_on_focus" + ,"plugin/script_splitter/editor/behaviour/can_expand_on_same_focus" + ,"plugin/script_splitter/editor/behaviour/smooth_expand" + ,"plugin/script_splitter/editor/behaviour/smooth_expand_time" + ,"plugin/script_splitter/line/size" + ,"plugin/script_splitter/line/color" + ,"plugin/script_splitter/line/draggable" + ,"plugin/script_splitter/line/expand_by_double_click" + ,"plugin/script_splitter/line/button/size" + ,"plugin/script_splitter/line/button/modulate" + ,"plugin/script_splitter/line/button/always_visible" + ] + + var settings : EditorSettings = EditorInterface.get_editor_settings() + var changes : PackedStringArray = settings.get_changed_settings() + + for c in changes: + if c in dt: + _setup() + update() + break + +func _setup() -> void: + + var settings : EditorSettings = EditorInterface.get_editor_settings() + if !settings.settings_changed.is_connected(_on_change): + settings.settings_changed.connect(_on_change) + + for x : Array in [ + ["behaviour_expand_on_focus", "plugin/script_splitter/editor/behaviour/expand_on_focus"] + ,["behaviour_can_expand_focus_same_container", "plugin/script_splitter/editor/behaviour/can_expand_on_same_focus"] + ,["behaviour_expand_smoothed", "plugin/script_splitter/editor/behaviour/smooth_expand"] + ,["drag_button_size", "plugin/script_splitter/editor/behaviour/smooth_expand_time"] + ,["separator_line_size", "plugin/script_splitter/line/size"] + ,["separator_line_color", "plugin/script_splitter/line/color"] + ,["behaviour_can_move_by_line", "plugin/script_splitter/line/draggable"] + ,["behaviour_expand_on_double_click", "plugin/script_splitter/line/expand_by_double_click"] + ,["drag_button_size", "plugin/script_splitter/line/button/size"] + ,["drag_button_modulate", "plugin/script_splitter/line/button/modulate"] + ,["drag_button_always_visible", "plugin/script_splitter/line/button/always_visible"] + ]: + if settings.has_setting(x[1]): + set(x[0], settings.get_setting(x[1])) + else: + settings.set_setting(x[1], get(x[0])) + + +func _enter_tree() -> void: + add_to_group(&"__ST_CS__") + super() + + if __setup: + return + + __setup = true + + _setup() + +func _exit_tree() -> void: + remove_from_group(&"__ST_CS__") + + var settings : EditorSettings = EditorInterface.get_editor_settings() + if settings.settings_changed.is_connected(_on_change): + settings.settings_changed.disconnect(_on_change) + diff --git a/addons/script_splitter/core/ui/splitter/splitter_root.gd.uid b/addons/script_splitter/core/ui/splitter/splitter_root.gd.uid new file mode 100644 index 00000000..532e88c3 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/splitter_root.gd.uid @@ -0,0 +1 @@ +uid://cl2dnbvuovoro diff --git a/addons/script_splitter/core/ui/splitter/taby/dottab.gd b/addons/script_splitter/core/ui/splitter/taby/dottab.gd new file mode 100644 index 00000000..cb473d1a --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/dottab.gd @@ -0,0 +1,78 @@ +@tool +extends TabBar +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +signal on_start_drag(t : TabBar) +signal on_stop_drag(t : TabBar) + +var is_drag : bool = false: + set(e): + is_drag = e + if is_drag: + Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN) + else: + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + +var _fms : float = 0.0 + +func reset() -> void: + if is_drag: + set_process(false) + is_drag = false + if is_inside_tree(): + on_stop_drag.emit(null) + +func _init() -> void: + if is_node_ready(): + _ready() + +func _ready() -> void: + set_process(false) + setup() + + select_with_rmb = true + +func _enter_tree() -> void: + if !is_in_group(&"__SPLITER_TAB__"): + add_to_group(&"__SPLITER_TAB__") + +func _exit_tree() -> void: + if is_in_group(&"__SPLITER_TAB__"): + remove_from_group(&"__SPLITER_TAB__") + +func _process(delta: float) -> void: + _fms += delta + if _fms > 0.24: + if is_drag: + if !Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + set_process(false) + is_drag = false + on_stop_drag.emit(self) + else: + on_start_drag.emit(self) + is_drag = true + +func setup() -> void: + if !gui_input.is_connected(_on_input): + gui_input.connect(_on_input) + if !is_in_group(&"__SPLITER_TAB__"): + add_to_group(&"__SPLITER_TAB__") + +func _on_input(e : InputEvent) -> void: + if e is InputEventMouseButton: + if e.button_index == MOUSE_BUTTON_LEFT: + is_drag = false + if e.pressed: + _fms = 0.0 + set_process(true) + else: + set_process(false) + if _fms >= 0.24: + on_stop_drag.emit(self) diff --git a/addons/script_splitter/core/ui/splitter/taby/dottab.gd.uid b/addons/script_splitter/core/ui/splitter/taby/dottab.gd.uid new file mode 100644 index 00000000..e2af9549 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/dottab.gd.uid @@ -0,0 +1 @@ +uid://bbwrffwhmepvw diff --git a/addons/script_splitter/core/ui/splitter/taby/overlay.gd b/addons/script_splitter/core/ui/splitter/taby/overlay.gd new file mode 100644 index 00000000..73ad96f0 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/overlay.gd @@ -0,0 +1,208 @@ +@tool +extends ColorRect +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const FILE_IN = preload("./../../../../assets/file_in.png") +const SPLIT_SELECTION = preload("./../../../../core/ui/splitter/taby/split_selection/SplitSelection.tscn") + +const NORMAL : float = 0.4 +const FILL : float = 0.65 + +var _dt : float = 0.0 +var _fc : float = 0.0 +var _ec : float = 1.0 + +var _ref : TabBar = null +var _container : Control = null +var _target : Control = null + +var _split_selection : Control = null + +var _type_split : StringName = &"" + +static var _busy : bool = false + +func get_type_split() -> StringName: + return _type_split + +func _init() -> void: + visible = false + mouse_filter = Control.MOUSE_FILTER_IGNORE + z_as_relative = false + z_index = RenderingServer.CANVAS_ITEM_Z_MAX - 1 + +func start(ref : TabBar) -> void: + _fc = NORMAL + _ec = FILL + _dt = 0.0 + _ref = ref + modulate.a = _fc + _target = null + + if is_instance_valid(ref): + _container = ref.get_parent() + else: + _container = null + + _update() + set_process(true) + +func _reset() -> void: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + if get_parent() != x: + reparent(x) + break + +static func _free() -> void: + var sc : SceneTree = Engine.get_main_loop() + if sc: + for __ : int in range(0, 5, 1): + await sc.process_frame + if !is_instance_valid(sc): + return + _busy = false + +func stop(tab : TabBar = null) -> bool: + set_process(false) + var out : bool = false + if !_busy and mouse_over(_target): + set_physics_process(true) + _busy = true + _type_split = &"" + _free.call_deferred() + if is_instance_valid(tab) and tab == _ref: + var container : Node = _ref.get_parent() + if is_instance_valid(_container) and _container == container: + out = get_global_rect().has_point(get_global_mouse_position()) + + for b : Node in _split_selection.get_buttons(): + if b is Control: + if !b.visible: + continue + if b.get_global_rect().has_point(get_global_mouse_position()): + _type_split = b.name + break + + visible = false + _container = null + _target = null + return out + +func get_container(ignore_self : bool = true) -> Node: + for x : Node in get_tree().get_nodes_in_group(&"__SC_SPLITTER__"): + if ignore_self and x == _container: + continue + var root : Node = x.get_parent() + if root is Control: + var rect : Rect2 = root.get_global_rect() + if rect.has_point(get_global_mouse_position()): + return x + return null + +func _ready() -> void: + color = Color.DARK_GREEN + + set_process(false) + set_physics_process(false) + visible = false + + set_anchors_preset(Control.PRESET_FULL_RECT) + + var cnt : Control = SPLIT_SELECTION.instantiate() + + add_child(cnt) + + cnt.size_flags_horizontal = Control.SIZE_EXPAND_FILL + cnt.size_flags_vertical = Control.SIZE_EXPAND_FILL + cnt.set_anchors_preset(Control.PRESET_FULL_RECT) + + _split_selection = cnt + +func mouse_over(control: Control) -> bool: + if null == control or !control.is_visible_in_tree(): + return false + + var control_window : Window = control.get_window() + + if control_window.get_window_id() != get_window().get_window_id(): + return false + + var mp : Vector2i = DisplayServer.mouse_get_position() + var mouse_window_id = DisplayServer.get_window_at_screen_position(mp) + + if mouse_window_id != control_window.get_window_id(): + return false + + return control.get_global_rect().has_point(control.get_global_mouse_position())#mp) + +func _update() -> void: + if is_instance_valid(_container): + var sc : SceneTree = Engine.get_main_loop() + if sc: + for x : Node in sc.get_nodes_in_group(&"__SC_SPLITTER__"): + if x is Control and mouse_over(x): + var same : bool = x == _container + + if same and (!(x is TabContainer) or x.get_child_count() < 2): + continue + + if !visible: + modulate.a = 0.0 + _fc = NORMAL + _ec = FILL + _dt = 0.0 + visible = true + + size = x.size + global_position = x.global_position + + + for y : Control in _split_selection.get_buttons(): + y.visible = same + + if _split_selection.file_texture: + _split_selection.file_texture.modulate.a = float(!same) + + _target = x + return + + _fc = NORMAL + _ec = FILL + _dt = 0.0 + modulate.a = _fc + _target = null + visible = false + +func _process(delta: float) -> void: + _update() + + if !visible: + return + + _dt += delta * 2.0 + if _dt >= 1.0: + modulate.a = _ec + if _ec == FILL: + _ec = NORMAL + _fc = FILL + else: + _ec = FILL + _fc = NORMAL + _dt = 0.0 + return + modulate.a = lerpf(_fc, _ec, _dt) + +func resize() -> void: + if !is_inside_tree(): + await tree_entered + + position = Vector2.ZERO + reset_size() + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical = Control.SIZE_EXPAND_FILL + #set_anchors_preset(Control.PRESET_FULL_RECT) diff --git a/addons/script_splitter/core/ui/splitter/taby/overlay.gd.uid b/addons/script_splitter/core/ui/splitter/taby/overlay.gd.uid new file mode 100644 index 00000000..d3fafe30 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/overlay.gd.uid @@ -0,0 +1 @@ +uid://lo116b5bry0t diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/SplitSelection.tscn b/addons/script_splitter/core/ui/splitter/taby/split_selection/SplitSelection.tscn new file mode 100644 index 00000000..59a25c33 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/SplitSelection.tscn @@ -0,0 +1,106 @@ +[gd_scene load_steps=10 format=3 uid="uid://8468n431hc6u"] + +[ext_resource type="Texture2D" uid="uid://cffqinddvan8k" path="res://addons/script_splitter/assets/sep_up.svg" id="1_jygbp"] +[ext_resource type="Script" uid="uid://bcf6213bhffkm" path="res://addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd" id="1_tifrm"] +[ext_resource type="Script" uid="uid://b2jjpfip8pcsf" path="res://addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd" id="2_tifrm"] +[ext_resource type="Texture2D" uid="uid://wp32fwv4flno" path="res://addons/script_splitter/assets/sep_left.svg" id="2_ttdhj"] +[ext_resource type="Texture2D" uid="uid://cxds5tr6aq5v3" path="res://addons/script_splitter/assets/file_in.png" id="2_w45p2"] +[ext_resource type="Texture2D" uid="uid://d1ee2fk3y8n83" path="res://addons/script_splitter/assets/sep_right.svg" id="3_tifrm"] +[ext_resource type="Texture2D" uid="uid://dt830c42xmul5" path="res://addons/script_splitter/assets/sep_bottom.svg" id="4_jevbt"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w45p2"] +bg_color = Color(0.09320182, 0.57214785, 0.68215865, 0.5) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 8 +border_color = Color(0.047066845, 0.37056562, 0.44495192, 0.5019608) +border_blend = true +corner_radius_top_left = 16 +corner_radius_top_right = 16 +corner_radius_bottom_right = 16 +corner_radius_bottom_left = 16 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2cbhf"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0, 0.91349006, 0.47941613, 0.5019608) +border_color = Color(4.813075e-07, 0.57995814, 0.19868338, 0.5019608) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[node name="SplitSelection" type="AspectRatioContainer" node_paths=PackedStringArray("file_texture")] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -108.0 +offset_top = -108.0 +offset_right = 108.0 +offset_bottom = 108.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_tifrm") +file_texture = NodePath("VB/HB/TextureRect") + +[node name="VB" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_constants/separation = 12 + +[node name="TOP" type="Button" parent="VB"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_w45p2") +theme_override_styles/hover = SubResource("StyleBoxFlat_2cbhf") +icon = ExtResource("1_jygbp") +icon_alignment = 1 +script = ExtResource("2_tifrm") + +[node name="HB" type="HBoxContainer" parent="VB"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="LEFT" type="Button" parent="VB/HB"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_w45p2") +theme_override_styles/hover = SubResource("StyleBoxFlat_2cbhf") +icon = ExtResource("2_ttdhj") +icon_alignment = 1 +script = ExtResource("2_tifrm") + +[node name="TextureRect" type="TextureRect" parent="VB/HB"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +texture = ExtResource("2_w45p2") +expand_mode = 3 +stretch_mode = 5 + +[node name="RIGHT" type="Button" parent="VB/HB"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_w45p2") +theme_override_styles/hover = SubResource("StyleBoxFlat_2cbhf") +icon = ExtResource("3_tifrm") +icon_alignment = 1 +script = ExtResource("2_tifrm") + +[node name="BOTTOM" type="Button" parent="VB"] +custom_minimum_size = Vector2(64, 64) +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_w45p2") +theme_override_styles/hover = SubResource("StyleBoxFlat_2cbhf") +icon = ExtResource("4_jevbt") +icon_alignment = 1 +script = ExtResource("2_tifrm") diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd b/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd new file mode 100644 index 00000000..ce432a39 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd @@ -0,0 +1,12 @@ +@tool +extends Button +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _ready() -> void: + owner.add_button(self) diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd.uid b/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd.uid new file mode 100644 index 00000000..96995b4a --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/btn.gd.uid @@ -0,0 +1 @@ +uid://b2jjpfip8pcsf diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/flat_box.tres b/addons/script_splitter/core/ui/splitter/taby/split_selection/flat_box.tres new file mode 100644 index 00000000..0e226e52 --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/flat_box.tres @@ -0,0 +1,13 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://cv7c0vox6s5ug"] + +[resource] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0, 0, 0, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd b/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd new file mode 100644 index 00000000..9cfc9f2f --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd @@ -0,0 +1,17 @@ +@tool +extends Control +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +@export var file_texture : TextureRect = null +var _btns : Array[Button] = [] + +func get_buttons() -> Array[Button]: + return _btns + +func add_button(b : Button) -> void: + _btns.append(b) diff --git a/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd.uid b/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd.uid new file mode 100644 index 00000000..cf2eddaf --- /dev/null +++ b/addons/script_splitter/core/ui/splitter/taby/split_selection/split_selection.gd.uid @@ -0,0 +1 @@ +uid://bcf6213bhffkm diff --git a/addons/script_splitter/core/ui/window/button.gd b/addons/script_splitter/core/ui/window/button.gd new file mode 100644 index 00000000..d3fe818d --- /dev/null +++ b/addons/script_splitter/core/ui/window/button.gd @@ -0,0 +1,13 @@ +@tool +extends Button +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +func _pressed() -> void: + if owner and owner.has_method(name): + owner.call(name) diff --git a/addons/script_splitter/core/ui/window/button.gd.uid b/addons/script_splitter/core/ui/window/button.gd.uid new file mode 100644 index 00000000..7424889d --- /dev/null +++ b/addons/script_splitter/core/ui/window/button.gd.uid @@ -0,0 +1 @@ +uid://d64ag8slx57b diff --git a/addons/script_splitter/core/ui/window/code_search.gd b/addons/script_splitter/core/ui/window/code_search.gd new file mode 100644 index 00000000..115ee889 --- /dev/null +++ b/addons/script_splitter/core/ui/window/code_search.gd @@ -0,0 +1,108 @@ +@tool +extends Panel + +# from: https://github.com/cherriesandmochi/gdmaim + +@export var code_edit : CodeEdit + +var _search_results : Array[Vector2i] +var _cur_search_result : int = 0 + + +func is_search_focused() -> bool: + return $VBoxContainer/Search.has_focus() + + +func open() -> void: + var node : Node = (get_viewport().gui_get_focus_owner()) + if node is CodeEdit: + code_edit = node + + if code_edit == null: + return + + + if code_edit.get_selected_text(): + $VBoxContainer/Search.text = code_edit.get_selected_text() + _update_search() + + show() + $VBoxContainer/Search.grab_focus() + + +func close() -> void: + hide() + if code_edit: + code_edit.grab_focus() + code_edit.set_search_text("") + code_edit.queue_redraw() + +func get_search_text() -> String: + return $VBoxContainer/Search.text + +func update_search() -> void: + var txt : String = get_search_text() + if txt.length() == 0: + return + _update_search(txt) + +func _update_search(_new_text : String = "") -> void: + var node : Node = (get_viewport().gui_get_focus_owner()) + if node is CodeEdit: + code_edit = node + + if code_edit == null: + close() + return + + var updated_text : String = $VBoxContainer/Search.text + + var flags : int = ( + TextEdit.SEARCH_MATCH_CASE * int($VBoxContainer/MatchCase.button_pressed) + + TextEdit.SEARCH_WHOLE_WORDS * int($VBoxContainer/WholeWords.button_pressed)) + + code_edit.set_search_text(updated_text) + code_edit.set_search_flags(flags) + code_edit.queue_redraw() + + _search_results.clear() + _cur_search_result = 0 + var result : Vector2i = code_edit.search(updated_text, flags, 0, 0) + while result.x != -1: + _search_results.append(result) + if result.y + 1 == code_edit.get_line_count(): + break + result = code_edit.search(updated_text, flags, result.y + 1, 0) + if _search_results.has(result): + break + + _update_matches() + + +func _update_matches() -> void: + $VBoxContainer/Matches.modulate = Color.WHITE + if !$VBoxContainer/Search.text: + $VBoxContainer/Matches.text = "" + elif !_search_results: + $VBoxContainer/Matches.text = "No match" + $VBoxContainer/Matches.modulate = Color.SALMON + else: + _cur_search_result = posmod(_cur_search_result, _search_results.size()) + $VBoxContainer/Matches.text = str(_cur_search_result + 1) + " of " + str(_search_results.size()) + " matches" + code_edit.set_caret_line(_search_results[_cur_search_result].y) + code_edit.set_caret_column(_search_results[_cur_search_result].x) + + +func _on_search_text_submitted(_new_text : String) -> void: + _cur_search_result += 1 + _update_matches() + + +func _on_previous_pressed() -> void: + _cur_search_result -= 1 + _update_matches() + + +func _on_next_pressed() -> void: + _cur_search_result += 1 + _update_matches() diff --git a/addons/script_splitter/core/ui/window/code_search.gd.uid b/addons/script_splitter/core/ui/window/code_search.gd.uid new file mode 100644 index 00000000..0b4c8b8d --- /dev/null +++ b/addons/script_splitter/core/ui/window/code_search.gd.uid @@ -0,0 +1 @@ +uid://bvqs6iskv4kx2 diff --git a/addons/script_splitter/core/ui/window/code_search.tscn b/addons/script_splitter/core/ui/window/code_search.tscn new file mode 100644 index 00000000..05057111 --- /dev/null +++ b/addons/script_splitter/core/ui/window/code_search.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=7 format=3 uid="uid://bomtra3fmv57j"] + +[ext_resource type="Script" uid="uid://bvqs6iskv4kx2" path="res://addons/script_splitter/core/ui/window/code_search.gd" id="1_dym63"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_58b8g"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_m7op6"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jjfj5"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7k12u"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dylah"] + +[node name="CodeSearch" type="Panel"] +custom_minimum_size = Vector2(0, 30) +script = ExtResource("1_dym63") + +[node name="VBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Search" type="LineEdit" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Matches" type="Label" parent="VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 13 + +[node name="Previous" type="Button" parent="VBoxContainer"] +layout_mode = 2 +theme_override_styles/focus = SubResource("StyleBoxEmpty_58b8g") +text = "<" +flat = true + +[node name="Next" type="Button" parent="VBoxContainer"] +layout_mode = 2 +theme_override_styles/focus = SubResource("StyleBoxEmpty_m7op6") +text = ">" +flat = true + +[node name="MatchCase" type="CheckBox" parent="VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 13 +theme_override_styles/focus = SubResource("StyleBoxEmpty_jjfj5") +text = "Match Case" +flat = true + +[node name="WholeWords" type="CheckBox" parent="VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 13 +theme_override_styles/focus = SubResource("StyleBoxEmpty_7k12u") +text = "Whole Words" +flat = true + +[node name="Close" type="Button" parent="VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/focus = SubResource("StyleBoxEmpty_dylah") +text = "X" +flat = true + +[connection signal="text_changed" from="VBoxContainer/Search" to="." method="_update_search"] +[connection signal="text_submitted" from="VBoxContainer/Search" to="." method="_on_search_text_submitted"] +[connection signal="pressed" from="VBoxContainer/Previous" to="." method="_on_previous_pressed"] +[connection signal="pressed" from="VBoxContainer/Next" to="." method="_on_next_pressed"] +[connection signal="pressed" from="VBoxContainer/MatchCase" to="." method="_update_search"] +[connection signal="pressed" from="VBoxContainer/WholeWords" to="." method="_update_search"] +[connection signal="pressed" from="VBoxContainer/Close" to="." method="close"] diff --git a/addons/script_splitter/core/ui/window/editor.gd b/addons/script_splitter/core/ui/window/editor.gd new file mode 100644 index 00000000..00cf6afd --- /dev/null +++ b/addons/script_splitter/core/ui/window/editor.gd @@ -0,0 +1,134 @@ +@tool +extends Window +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +const GRANT_KEY_CODES : PackedInt64Array = [ + KEY_S, KEY_SPACE, KEY_K, KEY_G, KEY_SLASH +] + +@export var _root : Node = null +@export var _search : Control = null + +func get_root() -> Node: + return _root + +func _ready() -> void: + _search.visible = false + set_physics_process(false) + + var _size : Vector2 = Engine.get_main_loop().root.size + _size = _size * 0.75 + _size.x = maxf(_size.x, 512.0) + _size.y = maxf(_size.y, 512.0) + size = _size + + show() + move_to_center() + $PanelContainer/VBoxContainer/HBoxContainer/always_top.button_pressed = always_on_top + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + _on_close() + +func _enter_tree() -> void: + if !close_requested.is_connected(_on_close): + close_requested.connect(_on_close) + + if !focus_entered.is_connected(_on_focus): + focus_entered.connect(_on_focus) + + if !focus_exited.is_connected(_out_focus): + focus_exited.connect(_out_focus) + + if !tree_exiting.is_connected(_on_close): + tree_exiting.connect(_on_close) + +func _out_focus() -> void: + always_on_top = $PanelContainer/VBoxContainer/HBoxContainer/always_top.button_pressed + +func setup() -> void: + if _root: + var x : Node = _root.get_child(1).get_child(0) + x.child_exiting_tree.connect(update) + +func _on_focus(__ : Variant = null) -> void: + always_on_top = false + _search.code_edit = null + _focus(_root) + _search.visible = _search.visible and null != _search.code_edit + +func _focus(n : Node, focus : bool = false) -> void: + if n: + if focus and n is Control: + var c : Control = n + if c.focus_mode != Control.FOCUS_NONE: + c.grab_focus.call_deferred() + + if c is CodeEdit: + _search.code_edit = c + if _search.visible: + _search.update_search() + + for x : Node in n.get_children(): + if x is TabContainer: + if !x.tab_changed.is_connected(_on_focus): + x.tab_changed.connect(_on_focus) + _focus(x.get_current_tab_control(), true) + break + _focus(x, focus) + + +func _on_close() -> void: + for x : Node in Engine.get_main_loop().get_nodes_in_group(&"__SCRIPT_SPLITTER__"): + x.call(&"remove_from_control", self) + + if !is_queued_for_deletion(): + queue_free() + +func _resizez(n : Node) -> void: + if n is Control: + if n.size > _root.size: + n.set_deferred(&"size", _root.size) + for x : Node in n.get_children(): + _resizez(x) + +func update(__ : Variant = null) -> void: + if _root.get_child_count() == 0: + queue_free() + return + call_deferred(&"set_physics_process", true) + +func _physics_process(__: float) -> void: + set_physics_process(false) + if !_root or _root.get_child_count() == 0 or _root.get_child(1).get_child(0).get_child_count() == 0: + queue_free() + return + + _resizez(_root) + +func _input(event: InputEvent) -> void: + if event is InputEventKey: + if event.ctrl_pressed and event.shift_pressed == false: + if event.keycode == KEY_F: + _search.open() + get_viewport().set_input_as_handled() + return + elif event.keycode in GRANT_KEY_CODES: + var vp : Viewport = (Engine.get_main_loop().root) + vp.push_input(event) + get_viewport().set_input_as_handled() + elif event.keycode == KEY_ESCAPE: + if _search.visible and (_search.has_focus() or _search.is_search_focused()): + _search.close() + get_viewport().set_input_as_handled() + +func center() -> void: + move_to_center() + +func always_top() -> void: + pass diff --git a/addons/script_splitter/core/ui/window/editor.gd.uid b/addons/script_splitter/core/ui/window/editor.gd.uid new file mode 100644 index 00000000..af70de1a --- /dev/null +++ b/addons/script_splitter/core/ui/window/editor.gd.uid @@ -0,0 +1 @@ +uid://b18w1ib1rv47t diff --git a/addons/script_splitter/core/ui/window/editor.tscn b/addons/script_splitter/core/ui/window/editor.tscn new file mode 100644 index 00000000..a903997d --- /dev/null +++ b/addons/script_splitter/core/ui/window/editor.tscn @@ -0,0 +1,63 @@ +[gd_scene load_steps=8 format=3 uid="uid://c1ou1s1ynw4nq"] + +[ext_resource type="Script" uid="uid://b18w1ib1rv47t" path="res://addons/script_splitter/core/ui/window/editor.gd" id="1_7ydjp"] +[ext_resource type="Script" uid="uid://dyo7c2g4uwn0g" path="res://addons/script_splitter/core/ui/splitter/splitter_container.gd" id="2_l3fsh"] +[ext_resource type="Texture2D" uid="uid://cejhhnje48450" path="res://addons/script_splitter/assets/fill_expand.svg" id="2_y3itc"] +[ext_resource type="PackedScene" uid="uid://bomtra3fmv57j" path="res://addons/script_splitter/core/ui/window/code_search.tscn" id="3_j866e"] +[ext_resource type="Texture2D" uid="uid://r6u1jtnbr4eg" path="res://addons/script_splitter/assets/atop.png" id="3_qfcg7"] +[ext_resource type="Script" uid="uid://d64ag8slx57b" path="res://addons/script_splitter/core/ui/window/button.gd" id="4_4imua"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j866e"] +bg_color = Color(0.21176471, 0.23921569, 0.2901961, 1) + +[node name="Editor" type="Window" node_paths=PackedStringArray("_root", "_search")] +auto_translate_mode = 2 +oversampling_override = 1.0 +title = "Script Splitter: Pop Script" +initial_position = 4 +size = Vector2i(968, 558) +script = ExtResource("1_7ydjp") +_root = NodePath("PanelContainer/VBoxContainer/MarginContainer") +_search = NodePath("PanelContainer/VBoxContainer/CodeSearch") + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_j866e") + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 + +[node name="center" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Center" +icon = ExtResource("2_y3itc") +flat = true +script = ExtResource("4_4imua") + +[node name="always_top" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +toggle_mode = true +text = "Always Top" +icon = ExtResource("3_qfcg7") +flat = true +script = ExtResource("4_4imua") + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 +script = ExtResource("2_l3fsh") + +[node name="CodeSearch" parent="PanelContainer/VBoxContainer" instance=ExtResource("3_j866e")] +visible = false +layout_mode = 2 diff --git a/addons/script_splitter/core/util/control.gd b/addons/script_splitter/core/util/control.gd new file mode 100644 index 00000000..3d2dfdc5 --- /dev/null +++ b/addons/script_splitter/core/util/control.gd @@ -0,0 +1,22 @@ +@tool +extends Node +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +signal notification(what : int) + +func _notification(what: int) -> void: + notification.emit(what) + +func panic() -> void: + if !tree_exiting.is_connected(_on_exiting): + tree_exiting.connect(_on_exiting) + +func _on_exiting() -> void: + notification.emit(NOTIFICATION_PREDELETE) diff --git a/addons/script_splitter/core/util/control.gd.uid b/addons/script_splitter/core/util/control.gd.uid new file mode 100644 index 00000000..07902e09 --- /dev/null +++ b/addons/script_splitter/core/util/control.gd.uid @@ -0,0 +1 @@ +uid://d3whf030xddd3 diff --git a/addons/script_splitter/plugin.cfg b/addons/script_splitter/plugin.cfg new file mode 100644 index 00000000..0d7a61ce --- /dev/null +++ b/addons/script_splitter/plugin.cfg @@ -0,0 +1,9 @@ +[plugin] + +name="Script Splitter" +description="Tool Addon for godot 4 +Allow split script window." +author="Twister" +version="0.5-DEV-3.0.0.1" +github="https://github.com/CodeNameTwister/Script-Splitter" +script="plugin.gd" diff --git a/addons/script_splitter/plugin.gd b/addons/script_splitter/plugin.gd new file mode 100644 index 00000000..2d440902 --- /dev/null +++ b/addons/script_splitter/plugin.gd @@ -0,0 +1,93 @@ +@tool +extends EditorPlugin +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Script Splitter +# https://github.com/CodeNameTwister/Script-Splitter +# +# Script Splitter addon for godot 4 +# author: "Twister" +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +const InputTool = preload("core/Input.gd") +const TWISTER_script_splitter = preload("core/builder.gd") +var builder : TWISTER_script_splitter = null +var handler : InputTool = null + +var tab_container : Node = null: + get: + if !is_instance_valid(tab_container): + var script_editor: ScriptEditor = EditorInterface.get_script_editor() + tab_container = find(script_editor, "*", "TabContainer") + return tab_container +var item_list : Node = null: + get: + if !is_instance_valid(item_list): + var script_editor: ScriptEditor = EditorInterface.get_script_editor() + item_list = find(script_editor, "*", "ItemList") + return item_list + +func find(root : Node, pattern : String, type : String) -> Node: + var e : Array[Node] = root.find_children(pattern, type, true, false) + if e.size() > 0: + return e[0] + return null + +func _enter_tree() -> void: + add_to_group(&"__SCRIPT_SPLITTER__") + builder = TWISTER_script_splitter.new() + handler = InputTool.new(self, builder) + +func script_split() -> void: + handler.get_honey_splitter().split() + +func script_merge(value : Node = null) -> void: + handler.get_honey_splitter().merge(value) + +func _ready() -> void: + set_process(false) + set_process_input(false) + for __ : int in range(5): + await Engine.get_main_loop().process_frame + if is_instance_valid(builder): + builder.init_1(self, tab_container, item_list) + if is_instance_valid(handler): + handler.init_1() + + builder.connect_callbacks( + handler.add_column, + handler.add_row, + handler.remove_column, + handler.remove_row, + handler.left_tab_close, + handler.right_tab_close, + handler.others_tab_close + ) + +func _save_external_data() -> void: + builder.refresh_warnings() + +func remove_from_control(control : Node) -> void: + builder.reset_by_control(control) + +func _exit_tree() -> void: + remove_from_group(&"__SCRIPT_SPLITTER__") + for x : Variant in [handler, builder]: + if is_instance_valid(x) and x is Object: + x.call(&"init_0") + +func get_builder() -> Object: + return builder + +func _process(delta: float) -> void: + builder.update(delta) + +func _input(event: InputEvent) -> void: + if handler.event(event): + get_viewport().set_input_as_handled() + +func _io_call(id : StringName) -> void: + builder.handle(id) + +func move_item_container(container : TabContainer, from : int, to : int) -> void: + builder.get_editor_manager().move_item_container(container, from, to) diff --git a/addons/script_splitter/plugin.gd.uid b/addons/script_splitter/plugin.gd.uid new file mode 100644 index 00000000..8dc92c76 --- /dev/null +++ b/addons/script_splitter/plugin.gd.uid @@ -0,0 +1 @@ +uid://dxveb5g4kxu45 diff --git a/addons/twitcher/COPYRIGHT.txt b/addons/twitcher/COPYRIGHT.txt new file mode 100644 index 00000000..c6df9355 --- /dev/null +++ b/addons/twitcher/COPYRIGHT.txt @@ -0,0 +1 @@ +Copyright Kemomi 2024 - 2025. All rights reserved. diff --git a/addons/twitcher/LICENSE b/addons/twitcher/LICENSE new file mode 100644 index 00000000..c3496514 --- /dev/null +++ b/addons/twitcher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Kani + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/twitcher/assets/api-icon.svg b/addons/twitcher/assets/api-icon.svg new file mode 100644 index 00000000..50790a17 --- /dev/null +++ b/addons/twitcher/assets/api-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/api-icon.svg.import b/addons/twitcher/assets/api-icon.svg.import new file mode 100644 index 00000000..958ae3dd --- /dev/null +++ b/addons/twitcher/assets/api-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://sp45xp1nleuk" +path="res://.godot/imported/api-icon.svg-16588d9bd32b7709dca1da03b9d3c51f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/api-icon.svg" +dest_files=["res://.godot/imported/api-icon.svg-16588d9bd32b7709dca1da03b9d3c51f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/auth-icon.svg b/addons/twitcher/assets/auth-icon.svg new file mode 100644 index 00000000..ccbf4754 --- /dev/null +++ b/addons/twitcher/assets/auth-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/auth-icon.svg.import b/addons/twitcher/assets/auth-icon.svg.import new file mode 100644 index 00000000..bd631cbc --- /dev/null +++ b/addons/twitcher/assets/auth-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1kwo4knd8h05" +path="res://.godot/imported/auth-icon.svg-8c3337a2de3e7408dd8b0185710eb409.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/auth-icon.svg" +dest_files=["res://.godot/imported/auth-icon.svg-8c3337a2de3e7408dd8b0185710eb409.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/chat-bot-icon.svg b/addons/twitcher/assets/chat-bot-icon.svg new file mode 100644 index 00000000..4cbdbba1 --- /dev/null +++ b/addons/twitcher/assets/chat-bot-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/twitcher/assets/chat-bot-icon.svg.import b/addons/twitcher/assets/chat-bot-icon.svg.import new file mode 100644 index 00000000..d8fc2343 --- /dev/null +++ b/addons/twitcher/assets/chat-bot-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4ew1g73lowsx" +path="res://.godot/imported/chat-bot-icon.svg-9d80847db47fbfdd74f6be711dc0155d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/chat-bot-icon.svg" +dest_files=["res://.godot/imported/chat-bot-icon.svg-9d80847db47fbfdd74f6be711dc0155d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/chat-icon.svg b/addons/twitcher/assets/chat-icon.svg new file mode 100644 index 00000000..fc93f642 --- /dev/null +++ b/addons/twitcher/assets/chat-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/chat-icon.svg.import b/addons/twitcher/assets/chat-icon.svg.import new file mode 100644 index 00000000..f3ca4452 --- /dev/null +++ b/addons/twitcher/assets/chat-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://txhld57vfpmo" +path="res://.godot/imported/chat-icon.svg-e1164fbfcc337348789c82b706287c92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/chat-icon.svg" +dest_files=["res://.godot/imported/chat-icon.svg-e1164fbfcc337348789c82b706287c92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/command-icon.svg b/addons/twitcher/assets/command-icon.svg new file mode 100644 index 00000000..11f8ff2d --- /dev/null +++ b/addons/twitcher/assets/command-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/command-icon.svg.import b/addons/twitcher/assets/command-icon.svg.import new file mode 100644 index 00000000..bf5ac4a6 --- /dev/null +++ b/addons/twitcher/assets/command-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr6bv6l3g4as3" +path="res://.godot/imported/command-icon.svg-29aa6e8d352722ad63fdf993d5c2472a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/command-icon.svg" +dest_files=["res://.godot/imported/command-icon.svg-29aa6e8d352722ad63fdf993d5c2472a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/default-1.png b/addons/twitcher/assets/default-1.png new file mode 100644 index 00000000..164a903b Binary files /dev/null and b/addons/twitcher/assets/default-1.png differ diff --git a/addons/twitcher/assets/default-1.png.import b/addons/twitcher/assets/default-1.png.import new file mode 100644 index 00000000..d10d6026 --- /dev/null +++ b/addons/twitcher/assets/default-1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co54e4bvuxyg4" +path="res://.godot/imported/default-1.png-78a76daccbc93f30d8b40ddeac645b35.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/default-1.png" +dest_files=["res://.godot/imported/default-1.png-78a76daccbc93f30d8b40ddeac645b35.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/default-2.png b/addons/twitcher/assets/default-2.png new file mode 100644 index 00000000..9ce5f905 Binary files /dev/null and b/addons/twitcher/assets/default-2.png differ diff --git a/addons/twitcher/assets/default-2.png.import b/addons/twitcher/assets/default-2.png.import new file mode 100644 index 00000000..86139bf2 --- /dev/null +++ b/addons/twitcher/assets/default-2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnde37257f2m6" +path="res://.godot/imported/default-2.png-861947cc28b4839d1945d487fbacae7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/default-2.png" +dest_files=["res://.godot/imported/default-2.png-861947cc28b4839d1945d487fbacae7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/default-4.png b/addons/twitcher/assets/default-4.png new file mode 100644 index 00000000..11e9219d Binary files /dev/null and b/addons/twitcher/assets/default-4.png differ diff --git a/addons/twitcher/assets/default-4.png.import b/addons/twitcher/assets/default-4.png.import new file mode 100644 index 00000000..430bf2e9 --- /dev/null +++ b/addons/twitcher/assets/default-4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cywqsjbcrlg4i" +path="res://.godot/imported/default-4.png-faf2fae7b309c98c06767c4c55575001.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/default-4.png" +dest_files=["res://.godot/imported/default-4.png-faf2fae7b309c98c06767c4c55575001.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/error-page.txt b/addons/twitcher/assets/error-page.txt new file mode 100644 index 00000000..8fae5f60 --- /dev/null +++ b/addons/twitcher/assets/error-page.txt @@ -0,0 +1,82 @@ + + + + + + + Twitcher - Login Failed + + + + + +
+
Login Failed
+
Login attempt was unsuccessful. Page should automatically close when it doesn't happen close it manually.
+ Close Page +
+ + diff --git a/addons/twitcher/assets/event-icon.svg b/addons/twitcher/assets/event-icon.svg new file mode 100644 index 00000000..0e22f0be --- /dev/null +++ b/addons/twitcher/assets/event-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/event-icon.svg.import b/addons/twitcher/assets/event-icon.svg.import new file mode 100644 index 00000000..11eff746 --- /dev/null +++ b/addons/twitcher/assets/event-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co7dy71iroidu" +path="res://.godot/imported/event-icon.svg-ae206972b8adb971cf3c156d8f980130.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/event-icon.svg" +dest_files=["res://.godot/imported/event-icon.svg-ae206972b8adb971cf3c156d8f980130.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/eventsub-icon.svg b/addons/twitcher/assets/eventsub-icon.svg new file mode 100644 index 00000000..6108d9a1 --- /dev/null +++ b/addons/twitcher/assets/eventsub-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/eventsub-icon.svg.import b/addons/twitcher/assets/eventsub-icon.svg.import new file mode 100644 index 00000000..2f58bacb --- /dev/null +++ b/addons/twitcher/assets/eventsub-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dykujenp3l608" +path="res://.godot/imported/eventsub-icon.svg-ce0c1dc80296ea912c84ec427215a2e3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/eventsub-icon.svg" +dest_files=["res://.godot/imported/eventsub-icon.svg-ce0c1dc80296ea912c84ec427215a2e3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/ext-link.svg b/addons/twitcher/assets/ext-link.svg new file mode 100644 index 00000000..b2a7aa88 --- /dev/null +++ b/addons/twitcher/assets/ext-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/ext-link.svg.import b/addons/twitcher/assets/ext-link.svg.import new file mode 100644 index 00000000..b0ee03af --- /dev/null +++ b/addons/twitcher/assets/ext-link.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwgk2bv7wbbo7" +path="res://.godot/imported/ext-link.svg-e60b226c6ba08585f6582dc226fddebb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/ext-link.svg" +dest_files=["res://.godot/imported/ext-link.svg-e60b226c6ba08585f6582dc226fddebb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/fallback_texture.tres b/addons/twitcher/assets/fallback_texture.tres new file mode 100644 index 00000000..aa1fdaf0 --- /dev/null +++ b/addons/twitcher/assets/fallback_texture.tres @@ -0,0 +1,9 @@ +[gd_resource type="GradientTexture1D" format=3 uid="uid://g1dbcjksbotw"] + +[sub_resource type="Gradient" id="Gradient_dsf6b"] +offsets = PackedFloat32Array(0) +colors = PackedColorArray(0.921569, 0.227451, 0.988235, 1) + +[resource] +gradient = SubResource("Gradient_dsf6b") +width = 1 diff --git a/addons/twitcher/assets/favicon.ico b/addons/twitcher/assets/favicon.ico new file mode 100644 index 00000000..e3f21d14 Binary files /dev/null and b/addons/twitcher/assets/favicon.ico differ diff --git a/addons/twitcher/assets/icon_search.tres b/addons/twitcher/assets/icon_search.tres new file mode 100644 index 00000000..b296b166 --- /dev/null +++ b/addons/twitcher/assets/icon_search.tres @@ -0,0 +1,13 @@ +[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://1e6nrtqsuc6"] + +[sub_resource type="Image" id="Image_mutbh"] +data = { +"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 225, 225, 68, 224, 224, 224, 184, 224, 224, 224, 240, 224, 224, 224, 232, 224, 224, 224, 186, 227, 227, 227, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 129, 224, 224, 224, 254, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 225, 225, 68, 224, 224, 224, 254, 224, 224, 224, 254, 224, 224, 224, 123, 224, 224, 224, 32, 224, 224, 224, 33, 225, 225, 225, 125, 224, 224, 224, 254, 224, 224, 224, 254, 226, 226, 226, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 225, 225, 125, 224, 224, 224, 255, 225, 225, 225, 174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 240, 224, 224, 224, 255, 231, 231, 231, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 226, 226, 226, 35, 224, 224, 224, 255, 224, 224, 224, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 232, 224, 224, 224, 255, 224, 224, 224, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 228, 228, 37, 224, 224, 224, 255, 224, 224, 224, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 186, 224, 224, 224, 255, 224, 224, 224, 123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 130, 224, 224, 224, 255, 224, 224, 224, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 227, 227, 227, 62, 224, 224, 224, 255, 224, 224, 224, 254, 225, 225, 225, 126, 225, 225, 225, 34, 227, 227, 227, 36, 224, 224, 224, 131, 224, 224, 224, 255, 224, 224, 224, 255, 226, 226, 226, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 122, 224, 224, 224, 254, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 226, 226, 226, 69, 225, 225, 225, 174, 224, 224, 224, 233, 224, 224, 224, 228, 224, 224, 224, 173, 226, 226, 226, 77, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 227, 225, 225, 225, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 231, 231, 231, 21, 225, 225, 225, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[resource] +image = SubResource("Image_mutbh") diff --git a/addons/twitcher/assets/media-loader-icon.svg b/addons/twitcher/assets/media-loader-icon.svg new file mode 100644 index 00000000..83610fa9 --- /dev/null +++ b/addons/twitcher/assets/media-loader-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/media-loader-icon.svg.import b/addons/twitcher/assets/media-loader-icon.svg.import new file mode 100644 index 00000000..e73bc122 --- /dev/null +++ b/addons/twitcher/assets/media-loader-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://drsj3w203jihf" +path="res://.godot/imported/media-loader-icon.svg-7569e70d457bf77b040db83e53346e05.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/media-loader-icon.svg" +dest_files=["res://.godot/imported/media-loader-icon.svg-7569e70d457bf77b040db83e53346e05.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/no_profile.png b/addons/twitcher/assets/no_profile.png new file mode 100644 index 00000000..3fe0e63c Binary files /dev/null and b/addons/twitcher/assets/no_profile.png differ diff --git a/addons/twitcher/assets/no_profile.png.import b/addons/twitcher/assets/no_profile.png.import new file mode 100644 index 00000000..aa62d16f --- /dev/null +++ b/addons/twitcher/assets/no_profile.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6nflfslr4a52" +path.s3tc="res://.godot/imported/no_profile.png-c0302c23dfe26865f13493d5d6d52fb6.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/twitcher/assets/no_profile.png" +dest_files=["res://.godot/imported/no_profile.png-c0302c23dfe26865f13493d5d6d52fb6.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=0 diff --git a/addons/twitcher/assets/redemption-icon.svg b/addons/twitcher/assets/redemption-icon.svg new file mode 100644 index 00000000..b7d42235 --- /dev/null +++ b/addons/twitcher/assets/redemption-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/addons/twitcher/assets/redemption-icon.svg.import b/addons/twitcher/assets/redemption-icon.svg.import new file mode 100644 index 00000000..88d214c3 --- /dev/null +++ b/addons/twitcher/assets/redemption-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr73s4l8at7pa" +path="res://.godot/imported/redemption-icon.svg-755a00bc0ddc52fd4d69893f24674931.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/redemption-icon.svg" +dest_files=["res://.godot/imported/redemption-icon.svg-755a00bc0ddc52fd4d69893f24674931.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/reward-icon.svg b/addons/twitcher/assets/reward-icon.svg new file mode 100644 index 00000000..f2385551 --- /dev/null +++ b/addons/twitcher/assets/reward-icon.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + diff --git a/addons/twitcher/assets/reward-icon.svg.import b/addons/twitcher/assets/reward-icon.svg.import new file mode 100644 index 00000000..6987aa4a --- /dev/null +++ b/addons/twitcher/assets/reward-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4ys5higjmfuy" +path="res://.godot/imported/reward-icon.svg-2b3b858c697d49d2cbdcd001fd8f96f1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/reward-icon.svg" +dest_files=["res://.godot/imported/reward-icon.svg-2b3b858c697d49d2cbdcd001fd8f96f1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/service-icon.svg b/addons/twitcher/assets/service-icon.svg new file mode 100644 index 00000000..1a339801 --- /dev/null +++ b/addons/twitcher/assets/service-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/service-icon.svg.import b/addons/twitcher/assets/service-icon.svg.import new file mode 100644 index 00000000..8335f5f8 --- /dev/null +++ b/addons/twitcher/assets/service-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvvl6migokgdw" +path="res://.godot/imported/service-icon.svg-e43d86792dba5abf158c9a90e8467717.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/service-icon.svg" +dest_files=["res://.godot/imported/service-icon.svg-e43d86792dba5abf158c9a90e8467717.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/success-page.txt b/addons/twitcher/assets/success-page.txt new file mode 100644 index 00000000..b12bf541 --- /dev/null +++ b/addons/twitcher/assets/success-page.txt @@ -0,0 +1,79 @@ + + + + + + + Twitcher - Login + + + + + +
+
Login Success
+
Page should automatically close when it doesn't happen close it manually.
+ Close Page +
+ + diff --git a/addons/twitcher/assets/transparent.tres b/addons/twitcher/assets/transparent.tres new file mode 100644 index 00000000..454e7776 --- /dev/null +++ b/addons/twitcher/assets/transparent.tres @@ -0,0 +1,9 @@ +[gd_resource type="GradientTexture1D" format=3 uid="uid://bdhuy21ldt2vv"] + +[sub_resource type="Gradient" id="Gradient_y3p12"] +offsets = PackedFloat32Array(1) +colors = PackedColorArray(1, 1, 1, 0) + +[resource] +gradient = SubResource("Gradient_y3p12") +width = 1 diff --git a/addons/twitcher/assets/twitcher-icon.svg b/addons/twitcher/assets/twitcher-icon.svg new file mode 100644 index 00000000..29e1ed80 --- /dev/null +++ b/addons/twitcher/assets/twitcher-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/twitcher/assets/twitcher-icon.svg.import b/addons/twitcher/assets/twitcher-icon.svg.import new file mode 100644 index 00000000..6dc6c88e --- /dev/null +++ b/addons/twitcher/assets/twitcher-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ctljyyw6gikq0" +path="res://.godot/imported/twitcher-icon.svg-eae8d458f370f3edcefb2f6360cb1ecc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/assets/twitcher-icon.svg" +dest_files=["res://.godot/imported/twitcher-icon.svg-eae8d458f370f3edcefb2f6360cb1ecc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/assets/twitcher_description_label_settings.tres b/addons/twitcher/assets/twitcher_description_label_settings.tres new file mode 100644 index 00000000..33951136 --- /dev/null +++ b/addons/twitcher/assets/twitcher_description_label_settings.tres @@ -0,0 +1,5 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://3kr4x2rxu4kd"] + +[resource] +font_size = 12 +font_color = Color(1, 1, 1, 0.5) diff --git a/addons/twitcher/assets/twitcher_editor_scopes.tres b/addons/twitcher/assets/twitcher_editor_scopes.tres new file mode 100644 index 00000000..0e4dd3db --- /dev/null +++ b/addons/twitcher/assets/twitcher_editor_scopes.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="TwitchOAuthScopes" load_steps=2 format=3 uid="uid://cgqldyna2cv5h"] + +[ext_resource type="Script" uid="uid://b3n3et8mebjcc" path="res://addons/twitcher/auth/twitch_oauth_scopes.gd" id="1_amyli"] + +[resource] +script = ExtResource("1_amyli") +used_scopes = Array[StringName]([&"channel:manage:redemptions"]) +metadata/_custom_type_script = "uid://b3n3et8mebjcc" diff --git a/addons/twitcher/assets/twitcher_info_label_settings.tres b/addons/twitcher/assets/twitcher_info_label_settings.tres new file mode 100644 index 00000000..4f8545fb --- /dev/null +++ b/addons/twitcher/assets/twitcher_info_label_settings.tres @@ -0,0 +1,4 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://d12dapnv7b00n"] + +[resource] +font_color = Color(0.400671, 0.976237, 1, 1) diff --git a/addons/twitcher/assets/twitcher_title_label_settings.tres b/addons/twitcher/assets/twitcher_title_label_settings.tres new file mode 100644 index 00000000..68f32c44 --- /dev/null +++ b/addons/twitcher/assets/twitcher_title_label_settings.tres @@ -0,0 +1,9 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://bnsxy6gcm8q11"] + +[sub_resource type="SystemFont" id="SystemFont_rtf3j"] +font_weight = 800 +force_autohinter = true + +[resource] +font = SubResource("SystemFont_rtf3j") +font_size = 18 diff --git a/addons/twitcher/assets/twitcher_warning_label_settings.tres b/addons/twitcher/assets/twitcher_warning_label_settings.tres new file mode 100644 index 00000000..5fa8bda4 --- /dev/null +++ b/addons/twitcher/assets/twitcher_warning_label_settings.tres @@ -0,0 +1,4 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://cng881nsuud80"] + +[resource] +font_color = Color(1, 0.870588, 0.4, 1) diff --git a/addons/twitcher/auth/preset_game_scopes.tres b/addons/twitcher/auth/preset_game_scopes.tres new file mode 100644 index 00000000..ec4e4632 --- /dev/null +++ b/addons/twitcher/auth/preset_game_scopes.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="TwitchOAuthScopes" load_steps=2 format=3 uid="uid://3dm6ts8hwlys"] + +[ext_resource type="Script" uid="uid://b3n3et8mebjcc" path="res://addons/twitcher/auth/twitch_oauth_scopes.gd" id="1_bpjq8"] + +[resource] +script = ExtResource("1_bpjq8") +used_scopes = Array[StringName]([&"user:read:chat", &"user:write:chat", &"user:bot"]) +metadata/_custom_type_script = "uid://b3n3et8mebjcc" diff --git a/addons/twitcher/auth/preset_overlay_scopes.tres b/addons/twitcher/auth/preset_overlay_scopes.tres new file mode 100644 index 00000000..2614e6de --- /dev/null +++ b/addons/twitcher/auth/preset_overlay_scopes.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="TwitchOAuthScopes" format=3 uid="uid://fcmfkstye4bq"] + +[ext_resource type="Script" uid="uid://b3n3et8mebjcc" path="res://addons/twitcher/auth/twitch_oauth_scopes.gd" id="1_2b4sa"] + +[resource] +script = ExtResource("1_2b4sa") +used_scopes = Array[StringName]([&"bits:read", &"channel:bot", &"channel:edit:commercial", &"channel:manage:moderators", &"channel:manage:polls", &"channel:manage:predictions", &"channel:manage:raids", &"channel:manage:redemptions", &"channel:manage:vips", &"channel:read:ads", &"channel:read:goals", &"channel:read:hype_train", &"channel:read:polls", &"channel:read:predictions", &"channel:read:redemptions", &"channel:read:subscriptions", &"channel:read:vips", &"moderation:read", &"moderator:manage:announcements", &"moderator:manage:banned_users", &"moderator:manage:chat_messages", &"moderator:manage:chat_settings", &"moderator:manage:shoutouts", &"moderator:manage:unban_requests", &"moderator:read:banned_users", &"moderator:read:chat_messages", &"moderator:read:chat_settings", &"moderator:read:chatters", &"moderator:read:followers", &"moderator:read:moderators", &"moderator:read:shoutouts", &"moderator:read:unban_requests", &"moderator:read:warnings", &"user:manage:chat_color", &"user:manage:whispers", &"user:read:broadcast", &"user:read:chat", &"user:read:email", &"user:read:emotes", &"user:read:follows", &"user:read:subscriptions", &"user:read:whispers", &"user:write:chat"]) diff --git a/addons/twitcher/auth/twitch_auth.gd b/addons/twitcher/auth/twitch_auth.gd new file mode 100644 index 00000000..9174adad --- /dev/null +++ b/addons/twitcher/auth/twitch_auth.gd @@ -0,0 +1,141 @@ +@icon("res://addons/twitcher/assets/auth-icon.svg") +@tool +extends Twitcher + +## Delegate class for the oOuch Library. +class_name TwitchAuth + +const HttpUtil = preload("res://addons/twitcher/lib/http/http_util.gd") + +static var _log: TwitchLogger = TwitchLogger.new("TwitchAuth") + +## The requested devicecode to show to the user for authorization +signal device_code_requested(device_code: OAuth.OAuthDeviceCodeResponse); + +## Where and how to authorize. +@export var oauth_setting: OAuthSetting: + set(val): + oauth_setting = val + if auth != null: auth.oauth_setting = oauth_setting + if token_handler != null: token_handler.oauth_setting = oauth_setting + update_configuration_warnings() +## Shows the what to authorize page of twitch again. (for example you need to relogin with a different account aka bot account) +@export var force_verify: bool +## Where should the tokens be saved into +@export var token: OAuthToken: + set(val): + token = val + if token_handler != null: token_handler.token = token + update_configuration_warnings() +## Scopes for the token that should be requested +@export var scopes: OAuthScopes: + set(val): + scopes = val + if auth != null: auth.scopes = scopes + update_configuration_warnings() + +## Takes care to authorize the user +var auth: OAuth +## Takes care to fetch and refresh oauth tokens +var token_handler: TwitchTokenHandler + + +var is_authenticated: bool: + get(): return auth.is_authenticated() + + +func _init() -> void: + child_entered_tree.connect(_on_enter_child) + # There could be better locations but this ensures that its there when an + # auth is needed. + var http_logger = TwitchLogger.new("Http") + HttpUtil.set_logger(http_logger.e, http_logger.i, http_logger.d) + + +func _on_enter_child(node: Node) -> void: + if node is OAuth: auth = node + if node is TwitchTokenHandler: token_handler = node + + +func _ready() -> void: + OAuth.set_logger(_log.e, _log.i, _log.d); + if oauth_setting == null: oauth_setting = create_default_oauth_setting() + _ensure_children() + + +func _ensure_children() -> void: + if token_handler == null: + token_handler = TwitchTokenHandler.new() + token_handler.name = "TokenHandler" + + if auth == null: + auth = OAuth.new() + auth.name = "OAuth" + + _sync_childs() + + if not auth.is_inside_tree(): + add_child(auth) + auth.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if not token_handler.is_inside_tree(): + add_child(token_handler) + token_handler.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + +func _sync_childs() -> void: + token_handler.oauth_setting = oauth_setting + token_handler.token = token + auth.token_handler = token_handler + auth.oauth_setting = oauth_setting + auth.scopes = scopes + auth.force_verify = &"true" if force_verify else &"false" + + +func authorize() -> bool: + _sync_childs() + if await auth.login(): + token_handler.process_mode = Node.PROCESS_MODE_INHERIT + return true + return false + + +func do_unsetup() -> void: + token_handler.revoke_token() + _log.d("revoked tokens on twitch side during unsetup") + + +func refresh_token() -> void: + auth.refresh_token() + + +static func create_default_oauth_setting() -> OAuthSetting: + var oauth_setting = OAuthSetting.new() + oauth_setting.authorization_flow = OAuth.AuthorizationFlow.AUTHORIZATION_CODE_FLOW + oauth_setting.device_authorization_url = "https://id.twitch.tv/oauth2/device" + oauth_setting.token_url = "https://id.twitch.tv/oauth2/token" + oauth_setting.authorization_url = "https://id.twitch.tv/oauth2/authorize" + oauth_setting.cache_file = "user://auth.conf" + oauth_setting.redirect_url = "http://localhost:7170" + return oauth_setting + + +## Checks if the correctly setup +func is_configured() -> bool: + return _get_configuration_warnings().is_empty() + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if oauth_setting == null: + result.append("OAuthSetting missing") + else: + var oauth_setting_problems : PackedStringArray = oauth_setting.get_valididation_problems() + if not oauth_setting_problems.is_empty(): + result.append("OAuthSetting is invalid") + result.append_array(oauth_setting_problems) + if scopes == null: + result.append("OAuthScopes is missing") + if token == null: + result.append("OAuthToken is missing") + return result diff --git a/addons/twitcher/auth/twitch_auth.gd.uid b/addons/twitcher/auth/twitch_auth.gd.uid new file mode 100644 index 00000000..129e0062 --- /dev/null +++ b/addons/twitcher/auth/twitch_auth.gd.uid @@ -0,0 +1 @@ +uid://iv0mgv0lu8b0 diff --git a/addons/twitcher/auth/twitch_oauth_scopes.gd b/addons/twitcher/auth/twitch_oauth_scopes.gd new file mode 100644 index 00000000..33944612 --- /dev/null +++ b/addons/twitcher/auth/twitch_oauth_scopes.gd @@ -0,0 +1,6 @@ +@tool +@icon("res://addons/twitcher/lib/oOuch/scope-icon.svg") +extends OAuthScopes + +## Technically the same like OAuthScopes just with a custom inspector for Twitch scopes +class_name TwitchOAuthScopes diff --git a/addons/twitcher/auth/twitch_oauth_scopes.gd.uid b/addons/twitcher/auth/twitch_oauth_scopes.gd.uid new file mode 100644 index 00000000..b9efd154 --- /dev/null +++ b/addons/twitcher/auth/twitch_oauth_scopes.gd.uid @@ -0,0 +1 @@ +uid://b3n3et8mebjcc diff --git a/addons/twitcher/auth/twitch_scope.gd b/addons/twitcher/auth/twitch_scope.gd new file mode 100644 index 00000000..7485bd0b --- /dev/null +++ b/addons/twitcher/auth/twitch_scope.gd @@ -0,0 +1,206 @@ +@tool +extends Resource + +class_name TwitchScope + +class Definition extends Object: + var value: StringName + var description: String + var categroy: String + + + func _init(val: StringName, desc: String, cat: String = "") -> void: + value = val + description = desc + categroy = cat + + + func get_category() -> String: + if categroy: return categroy + return value.substr(0, value.find(":")) + + + func _to_string() -> String: + return value + + +static var ANALYTICS_READ_EXTENSIONS = Definition.new(&"analytics:read:extensions", "View analytics data for the Twitch Extensions owned by the authenticated account.") +static var ANALYTICS_READ_GAMES = Definition.new(&"analytics:read:games", "View analytics data for the games owned by the authenticated account.") +static var BITS_READ = Definition.new(&"bits:read", "View Bits information for a channel.") +static var CHANNEL_BOT = Definition.new(&"channel:bot", "Joins your channel’s chatroom as a bot user, and perform chat-related actions as that user.") +static var CHANNEL_MANAGE_ADS = Definition.new(&"channel:manage:ads", "Manage ads schedule on a channel.") +static var CHANNEL_READ_ADS = Definition.new(&"channel:read:ads", "Read the ads schedule and details on your channel.") +static var CHANNEL_MANAGE_BROADCAST = Definition.new(&"channel:manage:broadcast", "Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags.") +static var CHANNEL_READ_CHARITY = Definition.new(&"channel:read:charity", "Read charity campaign details and user donations on your channel.") +static var CHANNEL_EDIT_COMMERCIAL = Definition.new(&"channel:edit:commercial", "Run commercials on a channel.") +static var CHANNEL_READ_EDITORS = Definition.new(&"channel:read:editors", "View a list of users with the editor role for a channel.") +static var CHANNEL_MANAGE_EXTENSIONS = Definition.new(&"channel:manage:extensions", "Manage a channel’s Extension configuration, including activating Extensions.") +static var CHANNEL_READ_GOALS = Definition.new(&"channel:read:goals", "View Creator Goals for a channel.") +static var CHANNEL_READ_GUEST_STAR = Definition.new(&"channel:read:guest_star", "Read Guest Star details for your channel.") +static var CHANNEL_MANAGE_GUEST_STAR = Definition.new(&"channel:manage:guest_star", "Manage Guest Star for your channel.") +static var CHANNEL_READ_HYPE_TRAIN = Definition.new(&"channel:read:hype_train", "View Hype Train information for a channel.") +static var CHANNEL_MANAGE_MODERATORS = Definition.new(&"channel:manage:moderators", "Add or remove the moderator role from users in your channel.") +static var CHANNEL_READ_POLLS = Definition.new(&"channel:read:polls", "View a channel’s polls.") +static var CHANNEL_MANAGE_POLLS = Definition.new(&"channel:manage:polls", "Manage a channel’s polls.") +static var CHANNEL_READ_PREDICTIONS = Definition.new(&"channel:read:predictions", "View a channel’s Channel Points Predictions.") +static var CHANNEL_MANAGE_PREDICTIONS = Definition.new(&"channel:manage:predictions", "Manage of channel’s Channel Points Predictions") +static var CHANNEL_MANAGE_RAIDS = Definition.new(&"channel:manage:raids", "Manage a channel raiding another channel.") +static var CHANNEL_READ_REDEMPTIONS = Definition.new(&"channel:read:redemptions", "View Channel Points custom rewards and their redemptions on a channel.") +static var CHANNEL_MANAGE_REDEMPTIONS = Definition.new(&"channel:manage:redemptions", "Manage Channel Points custom rewards and their redemptions on a channel.") +static var CHANNEL_MANAGE_SCHEDULE = Definition.new(&"channel:manage:schedule", "Manage a channel’s stream schedule.") +static var CHANNEL_READ_STREAM_KEY = Definition.new(&"channel:read:stream_key", "View an authorized user’s stream key.") +static var CHANNEL_READ_SUBSCRIPTIONS = Definition.new(&"channel:read:subscriptions", "View a list of all subscribers to a channel and check if a user is subscribed to a channel.") +static var CHANNEL_MANAGE_VIDEOS = Definition.new(&"channel:manage:videos", "Manage a channel’s videos, including deleting videos.") +static var CHANNEL_READ_VIPS = Definition.new(&"channel:read:vips", "Read the list of VIPs in your channel.") +static var CHANNEL_MANAGE_VIPS = Definition.new(&"channel:manage:vips", "Add or remove the VIP role from users in your channel.") +static var CLIPS_EDIT = Definition.new(&"clips:edit", "Manage Clips for a channel.") +static var MODERATION_READ = Definition.new(&"moderation:read", "View a channel’s moderation data including Moderators, Bans, Timeouts, and Automod settings.") +static var MODERATOR_MANAGE_ANNOUNCEMENTS = Definition.new(&"moderator:manage:announcements", "Send announcements in channels where you have the moderator role.") +static var MODERATOR_MANAGE_AUTOMOD = Definition.new(&"moderator:manage:automod", "Manage messages held for review by AutoMod in channels where you are a moderator.") +static var MODERATOR_READ_AUTOMOD_SETTINGS = Definition.new(&"moderator:read:automod_settings", "View a broadcaster’s AutoMod settings.") +static var MODERATOR_MANAGE_AUTOMOD_SETTINGS = Definition.new(&"moderator:manage:automod_settings", "Manage a broadcaster’s AutoMod settings.") +static var MODERATOR_READ_BANNED_USERS = Definition.new(&"moderator:read:banned_users", "Read the list of bans or unbans in channels where you have the moderator role.") +static var MODERATOR_MANAGE_BANNED_USERS = Definition.new(&"moderator:manage:banned_users", "Ban and unban users.") +static var MODERATOR_READ_BLOCKED_TERMS = Definition.new(&"moderator:read:blocked_terms", "View a broadcaster’s list of blocked terms.") +static var MODERATOR_READ_CHAT_MESSAGES = Definition.new(&"moderator:read:chat_messages", "Read deleted chat messages in channels where you have the moderator role.") +static var MODERATOR_MANAGE_BLOCKED_TERMS = Definition.new(&"moderator:manage:blocked_terms", "Manage a broadcaster’s list of blocked terms.") +static var MODERATOR_MANAGE_CHAT_MESSAGES = Definition.new(&"moderator:manage:chat_messages", "Delete chat messages in channels where you have the moderator role") +static var MODERATOR_READ_CHAT_SETTINGS = Definition.new(&"moderator:read:chat_settings", "View a broadcaster’s chat room settings.") +static var MODERATOR_MANAGE_CHAT_SETTINGS = Definition.new(&"moderator:manage:chat_settings", "Manage a broadcaster’s chat room settings.") +static var MODERATOR_READ_CHATTERS = Definition.new(&"moderator:read:chatters", "View the chatters in a broadcaster’s chat room.") +static var MODERATOR_READ_FOLLOWERS = Definition.new(&"moderator:read:followers", "Read the followers of a broadcaster.") +static var MODERATOR_READ_GUEST_STAR = Definition.new(&"moderator:read:guest_star", "Read Guest Star details for channels where you are a Guest Star moderator.") +static var MODERATOR_MANAGE_GUEST_STAR = Definition.new(&"moderator:manage:guest_star", "Manage Guest Star for channels where you are a Guest Star moderator.") +static var MODERATOR_READ_MODERATORS = Definition.new(&"moderator:read:moderators", "Read the list of moderators in channels where you have the moderator role.") +static var MODERATOR_READ_SHIELD_MODE = Definition.new(&"moderator:read:shield_mode", "View a broadcaster’s Shield Mode status.") +static var MODERATOR_MANAGE_SHIELD_MODE = Definition.new(&"moderator:manage:shield_mode", "Manage a broadcaster’s Shield Mode status.") +static var MODERATOR_READ_SHOUTOUTS = Definition.new(&"moderator:read:shoutouts", "View a broadcaster’s shoutouts.") +static var MODERATOR_MANAGE_SHOUTOUTS = Definition.new(&"moderator:manage:shoutouts", "Manage a broadcaster’s shoutouts.") +static var MODERATOR_READ_SUSPICIOUS_USERS = Definition.new(&"moderator:read:suspicious_users", "Read chat messages from suspicious users and see users flagged as suspicious in channels where you have the moderator role.") +static var MODERATOR_READ_UNBAN_REQUESTS = Definition.new(&"moderator:read:unban_requests", "View a broadcaster’s unban requests.") +static var MODERATOR_MANAGE_UNBAN_REQUESTS = Definition.new(&"moderator:manage:unban_requests", "Manage a broadcaster’s unban requests.") +static var MODERATOR_READ_VIPS = Definition.new(&"moderator:read:vips", "Read the list of VIPs in channels where you have the moderator role.") +static var MODERATOR_READ_WARNINGS = Definition.new(&"moderator:read:warnings", "Read warnings in channels where you have the moderator role.") +static var MODERATOR_MANAGE_WARNINGS = Definition.new(&"moderator:manage:warnings", "Warn users in channels where you have the moderator role.") +static var USER_BOT = Definition.new(&"user:bot", "Join a specified chat channel as your user and appear as a bot, and perform chat-related actions as your user.") +static var USER_EDIT = Definition.new(&"user:edit", "Manage a user object.") +static var USER_EDIT_BROADCAST = Definition.new(&"user:edit:broadcast", "View and edit a user’s broadcasting configuration, including Extension configurations.") +static var USER_READ_BLOCKED_USERS = Definition.new(&"user:read:blocked_users", "View the block list of a user.") +static var USER_MANAGE_BLOCKED_USERS = Definition.new(&"user:manage:blocked_users", "Manage the block list of a user.") +static var USER_READ_BROADCAST = Definition.new(&"user:read:broadcast", "View a user’s broadcasting configuration, including Extension configurations.") +static var USER_READ_CHAT = Definition.new(&"user:read:chat", "Receive chatroom messages and informational notifications relating to a channel’s chatroom.") +static var USER_MANAGE_CHAT_COLOR = Definition.new(&"user:manage:chat_color", "Update the color used for the user’s name in chat.") +static var USER_READ_EMAIL = Definition.new(&"user:read:email", "View a user’s email address.") +static var USER_READ_EMOTES = Definition.new(&"user:read:emotes", "View emotes available to a user") +static var USER_READ_FOLLOWS = Definition.new(&"user:read:follows", "View the list of channels a user follows.") +static var USER_READ_MODERATED_CHANNELS = Definition.new(&"user:read:moderated_channels", "Read the list of channels you have moderator privileges in.") +static var USER_READ_SUBSCRIPTIONS = Definition.new(&"user:read:subscriptions", "View if an authorized user is subscribed to specific channels.") +static var USER_READ_WHISPERS = Definition.new(&"user:read:whispers", "Receive whispers sent to your user.") +static var USER_MANAGE_WHISPERS = Definition.new(&"user:manage:whispers", "Receive whispers sent to your user, and send whispers on your user’s behalf.") +static var USER_WRITE_CHAT = Definition.new(&"user:write:chat", "Send chat messages to a chatroom.") +static var CHAT_READ = Definition.new(&"chat:edit", "Send chat messages to a chatroom using an IRC connection.", "IRC") +static var CHAT_EDIT = Definition.new(&"chat:read", "View chat messages sent in a chatroom using an IRC connection.", "IRC") + +## Key: Scope Name as String | Value: Definition +static var SCOPE_MAP: Dictionary = { + "analytics:read:extensions" = ANALYTICS_READ_EXTENSIONS, + "analytics:read:games" = ANALYTICS_READ_GAMES, + "bits:read" = BITS_READ, + "channel:bot" = CHANNEL_BOT, + "channel:manage:ads" = CHANNEL_MANAGE_ADS, + "channel:read:ads" = CHANNEL_READ_ADS, + "channel:manage:broadcast" = CHANNEL_MANAGE_BROADCAST, + "channel:read:charity" = CHANNEL_READ_CHARITY, + "channel:edit:commercial" = CHANNEL_EDIT_COMMERCIAL, + "channel:read:editors" = CHANNEL_READ_EDITORS, + "channel:manage:extensions" = CHANNEL_MANAGE_EXTENSIONS, + "channel:read:goals" = CHANNEL_READ_GOALS, + "channel:read:guest_star" = CHANNEL_READ_GUEST_STAR, + "channel:manage:guest_star" = CHANNEL_MANAGE_GUEST_STAR, + "channel:read:hype_train" = CHANNEL_READ_HYPE_TRAIN, + "channel:manage:moderators" = CHANNEL_MANAGE_MODERATORS, + "channel:read:polls" = CHANNEL_READ_POLLS, + "channel:manage:polls" = CHANNEL_MANAGE_POLLS, + "channel:read:predictions" = CHANNEL_READ_PREDICTIONS, + "channel:manage:predictions" = CHANNEL_MANAGE_PREDICTIONS, + "channel:manage:raids" = CHANNEL_MANAGE_RAIDS, + "channel:read:redemptions" = CHANNEL_READ_REDEMPTIONS, + "channel:manage:redemptions" = CHANNEL_MANAGE_REDEMPTIONS, + "channel:manage:schedule" = CHANNEL_MANAGE_SCHEDULE, + "channel:read:stream_key" = CHANNEL_READ_STREAM_KEY, + "channel:read:subscriptions" = CHANNEL_READ_SUBSCRIPTIONS, + "channel:manage:videos" = CHANNEL_MANAGE_VIDEOS, + "channel:read:vips" = CHANNEL_READ_VIPS, + "channel:manage:vips" = CHANNEL_MANAGE_VIPS, + "clips:edit" = CLIPS_EDIT, + "moderation:read" = MODERATION_READ, + "moderator:manage:announcements" = MODERATOR_MANAGE_ANNOUNCEMENTS, + "moderator:manage:automod" = MODERATOR_MANAGE_AUTOMOD, + "moderator:read:automod_settings" = MODERATOR_READ_AUTOMOD_SETTINGS, + "moderator:manage:automod_settings" = MODERATOR_MANAGE_AUTOMOD_SETTINGS, + "moderator:read:banned_users" = MODERATOR_READ_BANNED_USERS, + "moderator:manage:banned_users" = MODERATOR_MANAGE_BANNED_USERS, + "moderator:read:blocked_terms" = MODERATOR_READ_BLOCKED_TERMS, + "moderator:read:chat_messages" = MODERATOR_READ_CHAT_MESSAGES, + "moderator:manage:blocked_terms" = MODERATOR_MANAGE_BLOCKED_TERMS, + "moderator:manage:chat_messages" = MODERATOR_MANAGE_CHAT_MESSAGES, + "moderator:read:chat_settings" = MODERATOR_READ_CHAT_SETTINGS, + "moderator:manage:chat_settings" = MODERATOR_MANAGE_CHAT_SETTINGS, + "moderator:read:chatters" = MODERATOR_READ_CHATTERS, + "moderator:read:followers" = MODERATOR_READ_FOLLOWERS, + "moderator:read:guest_star" = MODERATOR_READ_GUEST_STAR, + "moderator:manage:guest_star" = MODERATOR_MANAGE_GUEST_STAR, + "moderator:read:moderators" = MODERATOR_READ_MODERATORS, + "moderator:read:shield_mode" = MODERATOR_READ_SHIELD_MODE, + "moderator:manage:shield_mode" = MODERATOR_MANAGE_SHIELD_MODE, + "moderator:read:shoutouts" = MODERATOR_READ_SHOUTOUTS, + "moderator:manage:shoutouts" = MODERATOR_MANAGE_SHOUTOUTS, + "moderator:read:suspicious_users" = MODERATOR_READ_SUSPICIOUS_USERS, + "moderator:read:unban_requests" = MODERATOR_READ_UNBAN_REQUESTS, + "moderator:manage:unban_requests" = MODERATOR_MANAGE_UNBAN_REQUESTS, + "moderator:read:vips" = MODERATOR_READ_VIPS, + "moderator:read:warnings" = MODERATOR_READ_WARNINGS, + "moderator:manage:warnings" = MODERATOR_MANAGE_WARNINGS, + "user:bot" = USER_BOT, + "user:edit" = USER_EDIT, + "user:edit:broadcast" = USER_EDIT_BROADCAST, + "user:read:blocked_users" = USER_READ_BLOCKED_USERS, + "user:manage:blocked_users" = USER_MANAGE_BLOCKED_USERS, + "user:read:broadcast" = USER_READ_BROADCAST, + "user:read:chat" = USER_READ_CHAT, + "user:manage:chat_color" = USER_MANAGE_CHAT_COLOR, + "user:read:email" = USER_READ_EMAIL, + "user:read:emotes" = USER_READ_EMOTES, + "user:read:follows" = USER_READ_FOLLOWS, + "user:read:moderated_channels" = USER_READ_MODERATED_CHANNELS, + "user:read:subscriptions" = USER_READ_SUBSCRIPTIONS, + "user:read:whispers" = USER_READ_WHISPERS, + "user:manage:whispers" = USER_MANAGE_WHISPERS, + "user:write:chat" = USER_WRITE_CHAT, + "chat:read" = CHAT_READ, + "chat:edit" = CHAT_EDIT, +} + + +## Key: Category as String, value as Array[Definition] +static func get_grouped_scopes() -> Dictionary: + var result = {} + for scope: Definition in get_all_scopes(): + var category_name = scope.get_category() + var category = result.get_or_add(category_name, []) + category.append(scope) + + for scopes_in_category: Array in result.values(): + scopes_in_category.sort_custom(sort_scopes) + return result + + +static func sort_scopes(scope1: Definition, scope2: Definition) -> bool: + return String(scope1.value) < String(scope2.value) + + +static func get_all_scopes() -> Array[Definition]: + var scopes: Array[Definition] = [] + for scope in SCOPE_MAP.values(): + scopes.append(scope) + return scopes diff --git a/addons/twitcher/auth/twitch_scope.gd.uid b/addons/twitcher/auth/twitch_scope.gd.uid new file mode 100644 index 00000000..4adcea2f --- /dev/null +++ b/addons/twitcher/auth/twitch_scope.gd.uid @@ -0,0 +1 @@ +uid://bjgpdxggorn0l diff --git a/addons/twitcher/auth/twitch_token_handler.gd b/addons/twitcher/auth/twitch_token_handler.gd new file mode 100644 index 00000000..7f0572c4 --- /dev/null +++ b/addons/twitcher/auth/twitch_token_handler.gd @@ -0,0 +1,51 @@ +@icon("res://addons/twitcher/assets/auth-icon.svg") +@tool +extends OAuthTokenHandler + +class_name TwitchTokenHandler + +## Time between checking the validation of the tokens +var _last_validation_check: int = 0 + + +func _ready() -> void: + super._ready() + # We don't need to check right after the start + _last_validation_check = Time.get_ticks_msec() + 60 * 60 * 1000; + + +func _check_token_refresh() -> void: + super._check_token_refresh() + + if _last_validation_check < Time.get_ticks_msec(): + var token = OAuth.DEBUGGER_PROTECTION + await token.get_access_token() + validate_token(token) + + +## Calles the validation endpoint of Twtich to make sure +func validate_token(token: String) -> BufferedHTTPClient.ResponseData: + _last_validation_check = Time.get_ticks_msec() + 60 * 60 * 1000; + var validation_request = _http_client.request("https://id.twitch.tv/oauth2/validate", HTTPClient.METHOD_GET, { + "Authorization": "OAuth %s" % token.trim_prefix(OAuth.DEBUGGER_PROTECTION) + }, "") + var response: BufferedHTTPClient.ResponseData = await _http_client.wait_for_request(validation_request) + if response.response_code != 200: + refresh_tokens() + return response + + var response_string: String = response.response_data.get_string_from_utf8(); + var response_data: Variant = JSON.parse_string(response_string); + if response_data["expires_in"] <= 0: + refresh_tokens() + return response + + +func revoke_token() -> void: + var request = _http_client.request("https://id.twitch.tv/oauth2/revoke", HTTPClient.METHOD_POST, + { "Content-Type": "application/x-www-form-urlencoded" }, + "client_id=%s&token=%s" % [oauth_setting.client_id, await token.get_access_token()]) + var response: BufferedHTTPClient.ResponseData = await _http_client.wait_for_request(request) + if response.error: + var response_message = response.response_data.get_string_from_utf8() + logError("Couldn't revoke Token cause of: %s" % response_message) + token.remove_tokens() diff --git a/addons/twitcher/auth/twitch_token_handler.gd.uid b/addons/twitcher/auth/twitch_token_handler.gd.uid new file mode 100644 index 00000000..583ef51b --- /dev/null +++ b/addons/twitcher/auth/twitch_token_handler.gd.uid @@ -0,0 +1 @@ +uid://blnbogtrshw4r diff --git a/addons/twitcher/chat/twitch_announcement_color.gd b/addons/twitcher/chat/twitch_announcement_color.gd new file mode 100644 index 00000000..ed4f49c8 --- /dev/null +++ b/addons/twitcher/chat/twitch_announcement_color.gd @@ -0,0 +1,14 @@ +extends RefCounted + +class_name TwitchAnnouncementColor + +static var BLUE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("blue") +static var GREEN: TwitchAnnouncementColor = TwitchAnnouncementColor.new("green") +static var ORANGE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("orange") +static var PURPLE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("purple") +static var PRIMARY: TwitchAnnouncementColor = TwitchAnnouncementColor.new("primary") + +var value; + +func _init(color: String) -> void: + value = color; diff --git a/addons/twitcher/chat/twitch_announcement_color.gd.uid b/addons/twitcher/chat/twitch_announcement_color.gd.uid new file mode 100644 index 00000000..0d83a8b6 --- /dev/null +++ b/addons/twitcher/chat/twitch_announcement_color.gd.uid @@ -0,0 +1 @@ +uid://doop8abj8sed6 diff --git a/addons/twitcher/chat/twitch_bot.gd b/addons/twitcher/chat/twitch_bot.gd new file mode 100644 index 00000000..7a90d78f --- /dev/null +++ b/addons/twitcher/chat/twitch_bot.gd @@ -0,0 +1,123 @@ +@icon("res://addons/twitcher/assets/chat-bot-icon.svg") +@tool +extends Node + +## Helper to send messages with a second bot user and the corrosponding bot badge. +## Take care that setup is needed that it actually works! The right scopes for the target channel +## and the sender user has to be set. +## See also https://dev.twitch.tv/docs/api/reference/#send-chat-message +class_name TwitchBot + +static var _log: TwitchLogger = TwitchLogger.new("TwitchBot") + +const TWITCH_BOT_SCOPES = preload("res://addons/twitcher/chat/twitch_bot_scopes.tres") + +## The base settings of the game / overlay the bot will inherit from +@export var oauth_setting: OAuthSetting: + set = _update_setting + +## A token used for the bot specificially +@export var bot_token: OAuthToken + +## Sender user that should be used. Requires user:bot scope from chatting user, +## and either channel:bot scope from broadcaster or moderator status. +@export var sender: TwitchUser: + set = _update_sender + +## Receive user you will need the channel:bot scope from broadcaster or moderator status. Fallbacks to sender. +@export var receiver: TwitchUser: + set = _update_receiver + +var _bot_auth: TwitchAuth +var _bot_api: TwitchAPI + + +func _init() -> void: + child_entered_tree.connect(_on_enter_child) + + +func _ready() -> void: + if bot_token == null: + bot_token = OAuthToken.new() + bot_token._identifier = "Bot-Token" + _ensure_children() + _update_setting(oauth_setting) + + +func _on_enter_child(node: Node) -> void: + if node is TwitchAuth: _bot_auth = node + if node is TwitchAPI: _bot_api = node + + +func _ensure_children() -> void: + if _bot_api == null: + _bot_api = TwitchAPI.new() + _bot_api.name = "BotApi" + add_child(_bot_api) + _bot_api.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if _bot_auth == null: + _bot_auth = TwitchAuth.new() + _bot_auth.name = "BotAuth" + _bot_auth.scopes = TWITCH_BOT_SCOPES + add_child(_bot_auth) + _bot_auth.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + _bot_auth.token = bot_token + _bot_api.token = bot_token + + # Reset the API. The default shouldn't be the Bot API. It has way to less scopes! + if TwitchAPI.instance == _bot_api: + TwitchAPI.instance = null + + +## Sends a message as the bot user the target broadcaster default to the sender user. +## for_source_only: see https://dev.twitch.tv/docs/api/reference/#send-chat-message +func send_message(message: String, reply_parent_message_id: String = "", for_source_only = true, broadcaster: TwitchUser = null) -> void: + if not _bot_auth.is_authenticated: await _bot_auth.authorize() + if broadcaster == null: + broadcaster = receiver if receiver != null else sender + _log.d("Send message from %s to %s: %s" % [sender.display_name, broadcaster.display_name, message]) + var body: TwitchSendChatMessage.Body = TwitchSendChatMessage.Body.create(broadcaster.id, sender.id, message) + if reply_parent_message_id != "": + body.reply_parent_message_id = reply_parent_message_id + var response: TwitchSendChatMessage.Response = await _bot_api.send_chat_message(body) + for data in response.data: + if data.is_sent: _log.d("Message was sent %s" % [data.message_id]) + else: _log.e("Message couldn't be send cause of [%s]: %s" % [data.drop_reason.code, data.drop_reason.message]) + + +func _update_setting(val: OAuthSetting) -> void: + oauth_setting = val + update_configuration_warnings() + + if oauth_setting == null || not is_inside_tree(): return + + var bot_setting = oauth_setting.duplicate() + bot_setting.authorization_flow = OAuth.AuthorizationFlow.CLIENT_CREDENTIALS + _bot_auth.oauth_setting = bot_setting + _bot_api.oauth_setting = bot_setting + + +func _update_sender(val: TwitchUser) -> void: + sender = val + update_configuration_warnings() + + +func _update_receiver(val: TwitchUser) -> void: + receiver = val + update_configuration_warnings() + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if oauth_setting == null: + result.append("Proper OAuth settings are needed.") + elif oauth_setting.client_secret == "": + result.append("Client Secret is needed for using the bot node.") + + if sender == null: + result.append("Sender is missing.") + elif receiver != null && sender.id == receiver.id: + result.append("Sender and receiver are the same -> you won't see the bot badge.") + return result diff --git a/addons/twitcher/chat/twitch_bot.gd.uid b/addons/twitcher/chat/twitch_bot.gd.uid new file mode 100644 index 00000000..6dcef807 --- /dev/null +++ b/addons/twitcher/chat/twitch_bot.gd.uid @@ -0,0 +1 @@ +uid://bf3mvh373suhc diff --git a/addons/twitcher/chat/twitch_bot_scopes.tres b/addons/twitcher/chat/twitch_bot_scopes.tres new file mode 100644 index 00000000..94590be0 --- /dev/null +++ b/addons/twitcher/chat/twitch_bot_scopes.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="TwitchOAuthScopes" format=3 uid="uid://b8735fmqjt8up"] + +[ext_resource type="Script" uid="uid://b3n3et8mebjcc" path="res://addons/twitcher/auth/twitch_oauth_scopes.gd" id="1_2uhiy"] + +[resource] +script = ExtResource("1_2uhiy") +used_scopes = Array[StringName]([&"channel:bot", &"user:write:chat", &"user:bot"]) +metadata/_custom_type_script = "uid://b3n3et8mebjcc" diff --git a/addons/twitcher/chat/twitch_chat.gd b/addons/twitcher/chat/twitch_chat.gd new file mode 100644 index 00000000..3ec42e05 --- /dev/null +++ b/addons/twitcher/chat/twitch_chat.gd @@ -0,0 +1,116 @@ +@icon("../assets/chat-icon.svg") +@tool +extends Twitcher + +## Grants access to read and write to a chat +class_name TwitchChat + +static var _log: TwitchLogger = TwitchLogger.new("TwitchChat") + +static var instance: TwitchChat + +@export var broadcaster_user: TwitchUser: + set(val): + broadcaster_user = val + update_configuration_warnings() +## Can be null. Then the owner of the access token will be used to send message aka the current user. +@export var sender_user: TwitchUser +@export var media_loader: TwitchMediaLoader +@export var eventsub: TwitchEventsub: + set(val): + eventsub = val + update_configuration_warnings() +@export var api: TwitchAPI: + set(val): + api = val + update_configuration_warnings() + +## Should it subscribe on ready +@export var subscribe_on_ready: bool = true + + +## Triggered when a chat message got received +signal message_received(message: TwitchChatMessage) + + +func _ready() -> void: + _log.d("is ready") + if media_loader == null: media_loader = TwitchMediaLoader.instance + if api == null: api = TwitchAPI.instance + if eventsub == null: eventsub = TwitchEventsub.instance + eventsub.event.connect(_on_event_received) + if not Engine.is_editor_hint() && subscribe_on_ready: + subscribe() + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +## Subscribe to eventsub and preload data if not happend yet +func subscribe() -> void: + if broadcaster_user == null: + printerr("BroadcasterUser is not set. Can't subscribe to chat.") + return + + if is_instance_valid(media_loader): + media_loader.preload_badges(broadcaster_user.id) + media_loader.preload_emotes(broadcaster_user.id) + + for subscription: TwitchEventsubConfig in eventsub.get_subscriptions(): + if subscription.type == TwitchEventsubDefinition.Type.CHANNEL_CHAT_MESSAGE and \ + subscription.condition.broadcaster_user_id == broadcaster_user.id: + # it is already subscribed + return + + if sender_user == null: + var current_user: TwitchGetUsers.Response = await api.get_users(null) + sender_user = current_user.data[0] + + var config: TwitchEventsubConfig = TwitchEventsubConfig.new() + config.type = TwitchEventsubDefinition.Type.CHANNEL_CHAT_MESSAGE + config.condition = { + "broadcaster_user_id": broadcaster_user.id, + "user_id": sender_user.id + } + eventsub.subscribe(config) + _log.i("Listen to Chat of %s (%s)" % [broadcaster_user.display_name, broadcaster_user.id]) + + +func _on_event_received(type: StringName, data: Dictionary) -> void: + if type != TwitchEventsubDefinition.CHANNEL_CHAT_MESSAGE.value: return + var message: TwitchChatMessage = TwitchChatMessage.from_json(data) + if message.broadcaster_user_id == broadcaster_user.id: + message_received.emit(message) + + +func send_message(message: String, reply_parent_message_id: String = "") -> Array[TwitchSendChatMessage.ResponseData]: + var message_body: TwitchSendChatMessage.Body = TwitchSendChatMessage.Body.new() + message_body.broadcaster_id = broadcaster_user.id + message_body.sender_id = sender_user.id + message_body.message = message + if reply_parent_message_id: + message_body.reply_parent_message_id = reply_parent_message_id + + var response: TwitchSendChatMessage.Response = await api.send_chat_message(message_body) + if _log.enabled: + for message_data: TwitchSendChatMessage.ResponseData in response.data: + if not message_data.is_sent: + _log.w(message_data.drop_reason) + + return response.data + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if eventsub == null: + result.append("TwitchEventsub not assigned") + if api == null: + result.append("TwitchAPI not assigned") + if broadcaster_user == null: + result.append("Target broadcaster not specified") + return result diff --git a/addons/twitcher/chat/twitch_chat.gd.uid b/addons/twitcher/chat/twitch_chat.gd.uid new file mode 100644 index 00000000..c37b1398 --- /dev/null +++ b/addons/twitcher/chat/twitch_chat.gd.uid @@ -0,0 +1 @@ +uid://dcq1bvfrqimqq diff --git a/addons/twitcher/chat/twitch_chat_message.gd b/addons/twitcher/chat/twitch_chat_message.gd new file mode 100644 index 00000000..ee997191 --- /dev/null +++ b/addons/twitcher/chat/twitch_chat_message.gd @@ -0,0 +1,379 @@ +extends RefCounted + +class_name TwitchChatMessage + +enum FragmentType { + text = 0, + cheermote = 1, + emote = 2, + mention = 3 +} + +const FRAGMENT_TYPES: PackedStringArray = ["text", "cheermote", "emote", "mention"] + +enum EmoteFormat { + animated = 0, + _static = 1 +} + +const EMOTE_FORMATES: PackedStringArray = ["animated", "static"] + +enum MessageType { + ## Normal chat message + text = 0, + ## The default reward where the message is highlighted + channel_points_highlighted = 1, + ## Channel points were used to send this message in sub-only mode. + channel_points_sub_only = 2, + ## when a new user is typing for the first time + user_intro = 3, + ## When the power up message effect was used on this message + power_ups_message_effect = 4, + ## When a gigantified emote was posted + power_ups_gigantified_emote = 5 +} + +const MESSAGE_TYPES: PackedStringArray = ["text", "channel_points_highlighted", "channel_points_sub_only", "user_intro", "power_ups_message_effect", "power_ups_gigantified_emote"] + +class Message extends RefCounted: + ## The chat message in plain text. + var text: String + ## Ordered list of chat message fragments. + var fragments: Array[Fragment] = [] + + + static func from_json(d: Dictionary) -> Message: + var result = Message.new() + if d.has("text") and d["text"] != null: + result.text = d["text"] + if d.has("fragments") and d["fragments"] != null: + for value in d["fragments"]: + result.fragments.append(Fragment.from_json(value)) + return result + + +class Fragment extends RefCounted: + ## The type of message fragment. See "TwitchChatMessage.FRAGMENT_TYPE_*" + var type: FragmentType + ## Message text in fragment. + var text: String + ## Optional. Metadata pertaining to the cheermote. + var cheermote: Cheermote + ## Optional. Metadata pertaining to the emote. + var emote: Emote + ## Optional. Metadata pertaining to the mention. + var mention: Mention + + + static func from_json(d: Dictionary) -> Fragment: + var result = Fragment.new() + if d.has("type") and d["type"] != null: + result.type = FragmentType[d["type"]] + if d.has("text") and d["text"] != null: + result.text = d["text"] + if d.has("cheermote") and d["cheermote"] != null: + result.cheermote = Cheermote.from_json(d["cheermote"]) + if d.has("emote") and d["emote"] != null: + result.emote = Emote.from_json(d["emote"]) + if d.has("mention") and d["mention"] != null: + result.mention = Mention.from_json(d["mention"]) + return result + + +class Mention extends RefCounted: + ## The user ID of the mentioned user. + var user_id: String + ## The user name of the mentioned user. + var user_name: String + ## The user login of the mentioned user. + var user_login: String + + + static func from_json(d: Dictionary) -> Mention: + var result = Mention.new() + if d.has("user_id") and d["user_id"] != null: + result.user_id = d["user_id"] + if d.has("user_name") and d["user_name"] != null: + result.user_name = d["user_name"] + if d.has("user_login") and d["user_login"] != null: + result.user_login = d["user_login"] + return result + + +class Cheermote extends RefCounted: + ## The name portion of the Cheermote string that you use in chat to cheer Bits. The full Cheermote string is the concatenation of {prefix} + {number of Bits}. For example, if the prefix is “Cheer” and you want to cheer 100 Bits, the full Cheermote string is Cheer100. When the Cheermote string is entered in chat, Twitch converts it to the image associated with the Bits tier that was cheered. + var prefix: String + ## The amount of bits cheered. + var bits: int + ## The tier level of the cheermote. + var tier: int + + + func get_sprite_frames(_media_loader: TwitchMediaLoader, + scale: StringName = TwitchCheermoteDefinition.SCALE_1, + theme: StringName = TwitchCheermoteDefinition.THEME_DARK, + type: StringName = TwitchCheermoteDefinition.TYPE_STATIC) -> SpriteFrames: + var cheermote_definition: TwitchCheermoteDefinition = TwitchCheermoteDefinition.new(prefix, str(tier)) + cheermote_definition.scale = scale + cheermote_definition.theme = theme + cheermote_definition.type = type + var cheer_results: TwitchMediaLoader.CheerResult = await _media_loader.get_cheer_info(cheermote_definition) + return cheer_results.spriteframes + + + static func from_json(d: Dictionary) -> Cheermote: + var result = Cheermote.new() + if d.has("prefix") and d["prefix"] != null: + result.prefix = d["prefix"] + if d.has("bits") and d["bits"] != null: + result.bits = d["bits"] + if d.has("tier") and d["tier"] != null: + result.tier = d["tier"] + return result + + +class Emote extends RefCounted: + ## An ID that uniquely identifies this emote. + var id: String + ## An ID that identifies the emote set that the emote belongs to. + var emote_set_id: String + ## The ID of the broadcaster who owns the emote. + var owner_id: String + ## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only static. But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. See: "TwitchChatMessage.EMOTE_TYPE_*" + var format: Array[EmoteFormat] = [] + + + ## Resolves the spriteframes from this emote. Check `format` for possible formats. + ## Format: Defaults to animated when not available it uses static + ## Scale: 1, 2, 3 + func get_sprite_frames(_media_loader: TwitchMediaLoader, + scale: int = TwitchEmoteDefinition.SCALE_1, + theme: StringName = TwitchEmoteDefinition.THEME_DARK, + type: StringName = TwitchEmoteDefinition.TYPE_DEFAULT) -> SpriteFrames: + var definition: TwitchEmoteDefinition = TwitchEmoteDefinition.new(id) + definition.scale = scale + definition.theme = theme + definition.type = type + var emotes = await _media_loader.get_emotes_by_definition([definition]) + return emotes[definition] + + + static func from_json(d: Dictionary) -> Emote: + var result = Emote.new() + if d.has("id") and d["id"] != null: + result.id = d["id"] + if d.has("emote_set_id") and d["emote_set_id"] != null: + result.emote_set_id = d["emote_set_id"] + if d.has("owner_id") and d["owner_id"] != null: + result.owner_id = d["owner_id"] + if d.has("format") and d["format"] != null: + for format in d["format"]: + if format == "static": + result.format.append(EmoteFormat._static) + elif format == "animated": + result.format.append(EmoteFormat.animated) + return result + + + + +class Badge extends RefCounted: + ## An ID that identifies this set of chat badges. For example, Bits or Subscriber. + var set_id: String + ## An ID that identifies this version of the badge. The ID can be any value. For example, for Bits, the ID is the Bits tier level, but for World of Warcraft, it could be Alliance or Horde. + var id: String + ## Contains metadata related to the chat badges in the badges tag. Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber. + var info: String + + static func from_json(d: Dictionary) -> Badge: + var result = Badge.new() + if d.has("set_id") and d["set_id"] != null: + result.set_id = d["set_id"] + if d.has("id") and d["id"] != null: + result.id = d["id"] + if d.has("info") and d["info"] != null: + result.info = d["info"] + return result + + + +class Cheer extends RefCounted: + ## The amount of Bits the user cheered. + var bits: int + + + static func from_json(d: Dictionary) -> Cheer: + var result = Cheer.new() + if d.has("bits") and d["bits"] != null: + result.bits = d["bits"] + return result + +class Reply extends RefCounted: + ## An ID that uniquely identifies the parent message that this message is replying to. + var parent_message_id: String + ## The message body of the parent message. + var parent_message_body: String + ## User ID of the sender of the parent message. + var parent_user_id: String + ## User name of the sender of the parent message. + var parent_user_name: String + ## User login of the sender of the parent message. + var parent_user_login: String + ## An ID that identifies the parent message of the reply thread. + var thread_message_id: String + ## User ID of the sender of the thread’s parent message. + var thread_user_id: String + ## User name of the sender of the thread’s parent message. + var thread_user_name: String + ## User login of the sender of the thread’s parent message. + var thread_user_login: String + + static func from_json(d: Dictionary) -> Reply: + var result = Reply.new() + if d.has("parent_message_id") and d["parent_message_id"] != null: + result.parent_message_id = d["parent_message_id"] + if d.has("parent_message_body") and d["parent_message_body"] != null: + result.parent_message_body = d["parent_message_body"] + if d.has("parent_user_id") and d["parent_user_id"] != null: + result.parent_user_id = d["parent_user_id"] + if d.has("parent_user_name") and d["parent_user_name"] != null: + result.parent_user_name = d["parent_user_name"] + if d.has("parent_user_login") and d["parent_user_login"] != null: + result.parent_user_login = d["parent_user_login"] + if d.has("thread_message_id") and d["thread_message_id"] != null: + result.thread_message_id = d["thread_message_id"] + if d.has("thread_user_id") and d["thread_user_id"] != null: + result.thread_user_id = d["thread_user_id"] + if d.has("thread_user_name") and d["thread_user_name"] != null: + result.thread_user_name = d["thread_user_name"] + if d.has("thread_user_login") and d["thread_user_login"] != null: + result.thread_user_login = d["thread_user_login"] + return result + + +## The broadcaster user ID. +var broadcaster_user_id: String +## The broadcaster display name. +var broadcaster_user_name: String +## The broadcaster login. +var broadcaster_user_login: String +## The user ID of the user that sent the message. +var chatter_user_id: String +## The user name of the user that sent the message. +var chatter_user_name: String +## The user login of the user that sent the message. +var chatter_user_login: String +## A UUID that identifies the message. +var message_id: String +## The structured chat message. +var message: Message +## The type of message. +var message_type: MessageType +## List of chat badges. +var badges: Array[Badge] = [] +## Optional. Metadata if this message is a cheer. +var cheer: Cheer +## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #;. This tag may be empty if it is never set. +var color: String +## Optional. Metadata if this message is a reply. +var reply: Reply +## Optional. The ID of a channel points custom reward that was redeemed. +var channel_points_custom_reward_id: String +## Optional. The broadcaster user ID of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster. +var source_broadcaster_user_id: String +## Optional. The user name of the broadcaster of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster. +var source_broadcaster_user_name: String +## Optional. The login of the broadcaster of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster. +var source_broadcaster_user_login: String +## Optional. The UUID that identifies the source message from the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster. +var source_message_id: String +## Optional. The list of chat badges for the chatter in the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster. +var source_badges: Array[Badge] = [] + + +## Loads a chat message from Json decoded dictionary. TwitchService is optional in case images and badges should be load from the message. +static func from_json(d: Dictionary) -> TwitchChatMessage: + var result = TwitchChatMessage.new() + if d.has("broadcaster_user_id") and d["broadcaster_user_id"] != null: + result.broadcaster_user_id = d["broadcaster_user_id"] + if d.has("broadcaster_user_name") and d["broadcaster_user_name"] != null: + result.broadcaster_user_name = d["broadcaster_user_name"] + if d.has("broadcaster_user_login") and d["broadcaster_user_login"] != null: + result.broadcaster_user_login = d["broadcaster_user_login"] + if d.has("chatter_user_id") and d["chatter_user_id"] != null: + result.chatter_user_id = d["chatter_user_id"] + if d.has("chatter_user_name") and d["chatter_user_name"] != null: + result.chatter_user_name = d["chatter_user_name"] + if d.has("chatter_user_login") and d["chatter_user_login"] != null: + result.chatter_user_login = d["chatter_user_login"] + if d.has("message_id") and d["message_id"] != null: + result.message_id = d["message_id"] + if d.has("message") and d["message"] != null: + result.message = Message.from_json(d["message"]) + if d.has("message_type") and d["message_type"] != null: + result.message_type = MessageType[d["message_type"]] + if d.has("badges") and d["badges"] != null: + for value in d["badges"]: + result.badges.append(Badge.from_json(value)) + if d.has("cheer") and d["cheer"] != null: + result.cheer = Cheer.from_json(d["cheer"]) + if d.has("color") and d["color"] != null: + result.color = d["color"] + if d.has("reply") and d["reply"] != null: + result.reply = Reply.from_json(d["reply"]) + if d.has("channel_points_custom_reward_id") and d["channel_points_custom_reward_id"] != null: + result.channel_points_custom_reward_id = d["channel_points_custom_reward_id"] + if d.has("source_broadcaster_user_id") and d["source_broadcaster_user_id"] != null: + result.source_broadcaster_user_id = d["source_broadcaster_user_id"] + if d.has("source_broadcaster_user_name") and d["source_broadcaster_user_name"] != null: + result.source_broadcaster_user_name = d["source_broadcaster_user_name"] + if d.has("source_broadcaster_user_login") and d["source_broadcaster_user_login"] != null: + result.source_broadcaster_user_login = d["source_broadcaster_user_login"] + if d.has("source_message_id") and d["source_message_id"] != null: + result.source_message_id = d["source_message_id"] + if d.has("source_badges") and d["source_badges"] != null: + for value in d["source_badges"]: + result.source_badges.append(Badge.from_json(value)) + return result + + +## Key: TwitchBadgeDefinition | Value: SpriteFrames +func get_badges(_media_loader: TwitchMediaLoader, scale: int = TwitchBadgeDefinition.SCALE_1) -> Dictionary[TwitchBadgeDefinition, SpriteFrames]: + var definitions : Array[TwitchBadgeDefinition] = [] + for badge in badges: + var badge_definition : TwitchBadgeDefinition = TwitchBadgeDefinition.new(badge.set_id, badge.id, scale, broadcaster_user_id) + definitions.append(badge_definition) + var emotes : Dictionary[TwitchBadgeDefinition, SpriteFrames] = await _media_loader.get_badges(definitions) + return emotes + + +## Key: TwitchBadgeDefinition | Value: SpriteFrames +func get_source_badges(_media_loader: TwitchMediaLoader, scale: int = TwitchBadgeDefinition.SCALE_1) -> Dictionary[TwitchBadgeDefinition, SpriteFrames]: + var definitions : Array[TwitchBadgeDefinition] = [] + for badge in source_badges: + var badge_definition : TwitchBadgeDefinition = TwitchBadgeDefinition.new(badge.set_id, badge.id, scale, broadcaster_user_id) + definitions.append(badge_definition) + var emotes : Dictionary[TwitchBadgeDefinition, SpriteFrames] = await _media_loader.get_badges(definitions) + return emotes + +## Returns a the color of the user or the default when its not set never null +func get_color(default_color: String = "#AAAAAA") -> String: + return default_color if color == null || color == "" else color + + +## Preload all emojis in parallel to reduce loadtime +func load_emotes_from_fragment(_media_loader: TwitchMediaLoader, + scale: int = TwitchEmoteDefinition.SCALE_1, + theme: StringName = TwitchEmoteDefinition.THEME_DARK, + type: StringName = TwitchEmoteDefinition.TYPE_DEFAULT) -> Dictionary[TwitchEmoteDefinition, SpriteFrames]: + var emotes_to_load : Array[TwitchEmoteDefinition] = [] + + for fragment : TwitchChatMessage.Fragment in message.fragments: + match fragment.type: + TwitchChatMessage.FragmentType.emote: + var definition : TwitchEmoteDefinition = TwitchEmoteDefinition.new(fragment.emote.id) + definition.scale = scale + definition.theme = theme + definition.type = type + emotes_to_load.append(definition) + return await _media_loader.get_emotes_by_definition(emotes_to_load) diff --git a/addons/twitcher/chat/twitch_chat_message.gd.uid b/addons/twitcher/chat/twitch_chat_message.gd.uid new file mode 100644 index 00000000..e3f700a0 --- /dev/null +++ b/addons/twitcher/chat/twitch_chat_message.gd.uid @@ -0,0 +1 @@ +uid://bxu8no18dq2e3 diff --git a/addons/twitcher/chat/twitch_command.gd b/addons/twitcher/chat/twitch_command.gd new file mode 100644 index 00000000..9de67fe3 --- /dev/null +++ b/addons/twitcher/chat/twitch_command.gd @@ -0,0 +1,228 @@ +@icon("res://addons/twitcher/assets/command-icon.svg") +extends Twitcher + +# Untested yet +## A single command like !lurk +class_name TwitchCommand + +## Constant to convert from seconds to milliseconds +const S_TO_MS = 1000 + +static var ALL_COMMANDS: Array[TwitchCommand] = [] + +## Called when the command got received in the right format +signal command_received(from_username: String, info: TwitchCommandInfo, args: PackedStringArray) + +## Called when the command got received in the wrong format +signal received_invalid_command(from_username: String, info: TwitchCommandInfo, args: PackedStringArray) + +## Called when the user tries to use the command that is still on cooldown (remaining cooldown in seconds) +signal cooldown(from_username: String, info: TwitchCommandInfo, args: PackedStringArray, cooldown_remaining_in_s: float) + +## Required permission to execute the command +enum PermissionFlag { + EVERYONE = 0, + VIP = 1, + SUB = 2, + MOD = 4, + STREAMER = 8, + MOD_STREAMER = 12, # Mods and the streamer + NON_REGULAR = 15 # Everyone but regular viewers +} + +## Where the command should be accepted +enum WhereFlag { + CHAT = 1, + WHISPER = 2, + ANYWHERE = 3 +} + +@export var command_prefixes : Array[String] = ["!"] +## Name Command +@export var command: String +## Optional names of commands +@export var aliases: Array[String] +## Description for the user +@export_multiline var description: String + +## Minimal amount of argument 0 means no argument needed +@export var args_min: int = 0 +## Max amount of arguments -1 means infinite +@export var args_max: int = -1 +## Wich role of user is allowed to use it +@export var permission_level: PermissionFlag = PermissionFlag.EVERYONE +## Where is it allowed to use chat or whisper or both +@export var where: WhereFlag = WhereFlag.CHAT +## All allowed users empty array means everyone +@export var allowed_users: Array[String] = [] +## All chatrooms where the command listens to +@export var listen_to_chatrooms: Array[String] = [] +## Determines if the aliases and commands should be case sensitive or not +@export var case_insensitive: bool = true +## Cooldown per user +@export var user_cooldown: float = 0 +## Global cooldown for the command +@export var global_cooldown: float = 0 + +## The eventsub to listen for chatmessages +@export var eventsub: TwitchEventsub + +## Cooldowns per user Key: Username | Value: Time until it can be used again +var _user_cooldowns: Dictionary[String, float] = {} +## Time until it can be used again +var _global_cooldown: float + +static func create( + eventsub: TwitchEventsub, + cmd_name: String, + callable: Callable, + min_args: int = 0, + max_args: int = 0, + permission_level: int = PermissionFlag.EVERYONE, + where: int = WhereFlag.CHAT, + allowed_users: Array[String] = [], + listen_to_chatrooms: Array[String] = [], + case_insensitive: bool = true) -> TwitchCommand: + var command := TwitchCommand.new() + command.eventsub = eventsub + command.command = cmd_name + command.command_received.connect(callable) + command.args_min = min_args + command.args_max = max_args + command.permission_level = permission_level + command.where = where + command.allowed_users = allowed_users + command.listen_to_chatrooms = listen_to_chatrooms + command.case_insensitive = case_insensitive + return command + + +func _enter_tree() -> void: + if eventsub == null: eventsub = TwitchEventsub.instance + if eventsub != null: + eventsub.event.connect(_on_event) + ALL_COMMANDS.append(self) + + for prefix: String in command_prefixes: + if prefix.length() > 1: + push_error("TwitchCommand supports only 1 character for command prefix. '%s' is used by %s" % [prefix, get_path()]) + + +func _exit_tree() -> void: + if eventsub != null: + eventsub.event.disconnect(_on_event) + ALL_COMMANDS.erase(self) + + +func _on_event(type: StringName, data: Dictionary) -> void: + if type == TwitchEventsubDefinition.CHANNEL_CHAT_MESSAGE.value: + if where & WhereFlag.CHAT != WhereFlag.CHAT: return + var message : String = data.message.text + var username : String = data.chatter_user_login + var channel_name : String = data.broadcaster_user_login + if not _should_handle(message, username, channel_name): return + var chat_message = TwitchChatMessage.from_json(data) + _handle_command(username, message, channel_name, chat_message) + + if type == TwitchEventsubDefinition.USER_WHISPER_MESSAGE.value: + if where & WhereFlag.WHISPER != WhereFlag.WHISPER: return + var message : String = data.whisper.text + var from_user : String = data.from_user_login + if not _should_handle(message, from_user, from_user): return + _handle_command(from_user, message, data.to_user_login, data) + + +func add_alias(alias: String) -> void: + aliases.append(alias) + + +func _should_handle(message: String, username: String, channel_name: String) -> bool: + if not listen_to_chatrooms.is_empty() && not listen_to_chatrooms.has(channel_name): return false + if not allowed_users.is_empty() && not allowed_users.has(username): return false + if not command_prefixes.has(message.left(1)): return false + + # remove the command symbol in front + message = message.right(-1) + var split: PackedStringArray = message.split(" ", true, 1) + var current_command: String = split[0] + var alias_compare_function: Callable = func(cmd) -> bool: + if case_insensitive: + return current_command.nocasecmp_to(cmd) == 0 + else: + return current_command.casecmp_to(cmd) == 0 + + var is_alias: bool = aliases.any(alias_compare_function) + var is_command: bool = alias_compare_function.call(command) + + return is_command || is_alias + + + +func _handle_command(from_username: String, raw_message: String, to_user: String, data: Variant) -> void: + # remove the command symbol in front + raw_message = raw_message.right(-1) + var cmd_msg = raw_message.split(" ", true, 1) + var message = "" + var arg_array : PackedStringArray = [] + var command = cmd_msg[0] + var info = TwitchCommandInfo.new(self, to_user, from_username, arg_array, data) + + # Handle Permission check + var premission_required = permission_level != 0 + if premission_required: + var user_perm_flags = _get_perm_flag_from_tags(data) + if user_perm_flags & permission_level == 0: + received_invalid_command.emit(from_username, info, arg_array) + return + + # Handle Argument parsing + if cmd_msg.size() > 1: + message = cmd_msg[1] + arg_array.append_array(message.split(" ", false)) + var to_less_arguments = arg_array.size() < args_min + var to_much_arguments = arg_array.size() > args_max + if to_much_arguments && args_max != -1 || to_less_arguments: + received_invalid_command.emit(from_username, info, arg_array) + return + if arg_array.size() == 0 && args_min > 0: + received_invalid_command.emit(from_username, info, arg_array) + return + + # Handle Cooldowns + if user_cooldown > 0: + var current_user_cooldown: float = _user_cooldowns.get_or_add(from_username, 0) + if current_user_cooldown >= Time.get_ticks_msec(): + var cooldown_left: float = (current_user_cooldown - Time.get_ticks_msec()) / 1000 + cooldown.emit(from_username, info, arg_array, cooldown_left) + return + _user_cooldowns.set(from_username, Time.get_ticks_msec() + (user_cooldown * S_TO_MS)) + + if global_cooldown > 0: + if _global_cooldown >= Time.get_ticks_msec(): + var cooldown_left: float = (_global_cooldown - Time.get_ticks_msec()) / 1000 + cooldown.emit(from_username, info, arg_array, cooldown_left) + return + _global_cooldown = Time.get_ticks_msec() + (global_cooldown * S_TO_MS) + + # Executing the command + if arg_array.size() == 0: + var empty_args: Array[String] = [] + if args_max > 0: + command_received.emit(from_username, info, empty_args) + else: + command_received.emit(from_username, info, empty_args) + else: + command_received.emit(from_username, info, arg_array) + + +func _get_perm_flag_from_tags(data : Variant) -> int: + var flag: int = 0 + if data is TwitchChatMessage: + var message: TwitchChatMessage = data as TwitchChatMessage + for badge in message.badges: + match badge.set_id: + "broadcaster": flag += PermissionFlag.STREAMER + "vip": flag += PermissionFlag.VIP + "moderator": flag += PermissionFlag.MOD + "subscriber": flag += PermissionFlag.SUB + return flag diff --git a/addons/twitcher/chat/twitch_command.gd.uid b/addons/twitcher/chat/twitch_command.gd.uid new file mode 100644 index 00000000..6646c492 --- /dev/null +++ b/addons/twitcher/chat/twitch_command.gd.uid @@ -0,0 +1 @@ +uid://bmluckfvgm1c2 diff --git a/addons/twitcher/chat/twitch_command_help.gd b/addons/twitcher/chat/twitch_command_help.gd new file mode 100644 index 00000000..1214bd1b --- /dev/null +++ b/addons/twitcher/chat/twitch_command_help.gd @@ -0,0 +1,88 @@ +@icon("res://addons/twitcher/assets/command-icon.svg") +extends TwitchCommand + +class_name TwitchCommandHelp + +## Used to determine the Sender User if empty and to send the message back +@export var twitch_api: TwitchAPI +## Sender User that will send the answers on the command. Can be empty then the current user will be used +@export var sender_user: TwitchUser + +var _current_user: TwitchUser + + +func _enter_tree() -> void: + super._enter_tree() + if twitch_api == null: twitch_api = TwitchAPI.instance + if twitch_api == null: + push_error("Command is missing TwitchAPI to answer!") + return + + +func _ready() -> void: + if command == "": command = "help" + + command_received.connect(_on_command_receive) + + # Only when you have a user in the token otherwise twitch returns a 400 + if twitch_api.token.type == OAuthToken.USER_ACCESS_TOKEN: + var response: TwitchGetUsers.Response = await twitch_api.get_users(TwitchGetUsers.Opt.new()) + _current_user = response.data[0] + if sender_user == null: sender_user = _current_user + + +func _on_command_receive(from_username: String, info: TwitchCommandInfo, args: PackedStringArray) -> void: + if info.original_message is TwitchChatMessage: + var help_message: String = _generate_help_message(args, false) + var chat_message: TwitchChatMessage = info.original_message as TwitchChatMessage + var message_body: TwitchSendChatMessage.Body = TwitchSendChatMessage.Body.new() + message_body.broadcaster_id = chat_message.broadcaster_user_id + message_body.sender_id = sender_user.id + message_body.message = help_message + message_body.reply_parent_message_id = chat_message.message_id + twitch_api.send_chat_message(message_body) + else: + var help_message: String = _generate_help_message(args, true) + var message: Dictionary = info.original_message + if _current_user != null && message["to_user_id"] != _current_user.id: + push_error("Can't answer the whisper message. Receiver is not the user that will be used as sender!") + return + var message_body: TwitchSendWhisper.Body = TwitchSendWhisper.Body.new() + message_body.message = help_message + twitch_api.send_whisper(message_body, message["to_user_id"], message["from_user_id"]) + + +func _generate_help_message(args: Array[String], whisper_only: bool) -> String: + var message: String = "" + var show_details: bool = not args.is_empty() + + for command in TwitchCommand.ALL_COMMANDS: + if command == self: continue + var should_be_added: bool = command.where == TwitchCommand.WhereFlag.ANYWHERE \ + || command.where == TwitchCommand.WhereFlag.WHISPER && whisper_only \ + || command.where == TwitchCommand.WhereFlag.CHAT && not whisper_only + + if not args.is_empty(): + should_be_added = should_be_added && _is_command_in_args(command, args) + + if should_be_added: + if show_details: + message += "[%s%s - %s] " % [command.command_prefixes[0], command.command, command.description] + else: + message += "%s%s, " % [command.command_prefixes[0], command.command] + + if message == "": + return "No commands registered" + elif not show_details: + message = message.trim_suffix(", ") + message = "List of all Commands: %s | You can use '!help COMMAND' for details!" % message + return message + + +func _is_command_in_args(command: TwitchCommand, args: Array[String]) -> bool: + for arg in args: + if command.command == arg: + return true + if command.aliases.has(arg): + return true + return false diff --git a/addons/twitcher/chat/twitch_command_help.gd.uid b/addons/twitcher/chat/twitch_command_help.gd.uid new file mode 100644 index 00000000..80755882 --- /dev/null +++ b/addons/twitcher/chat/twitch_command_help.gd.uid @@ -0,0 +1 @@ +uid://ch0rxi1ogjx3q diff --git a/addons/twitcher/chat/twitch_command_info.gd b/addons/twitcher/chat/twitch_command_info.gd new file mode 100644 index 00000000..60e025a9 --- /dev/null +++ b/addons/twitcher/chat/twitch_command_info.gd @@ -0,0 +1,25 @@ +extends RefCounted + +## Meta information about the command sender +class_name TwitchCommandInfo + + +var command : TwitchCommand +var channel_name : String +var username : String +var arguments : Array[String] +## Depending on the type it's either a TwitchChatMessage or a Dictionary of the whisper message data +var original_message : Variant + + +func _init( + _command: TwitchCommand, + _channel_name: String, + _username: String, + _arguments: Array[String], + _original_message: Variant): + command = _command + channel_name = _channel_name + username = _username + arguments = _arguments + original_message = _original_message diff --git a/addons/twitcher/chat/twitch_command_info.gd.uid b/addons/twitcher/chat/twitch_command_info.gd.uid new file mode 100644 index 00000000..7aa497f8 --- /dev/null +++ b/addons/twitcher/chat/twitch_command_info.gd.uid @@ -0,0 +1 @@ +uid://c5k8j4ag3n0su diff --git a/addons/twitcher/default_oauth_token.tres b/addons/twitcher/default_oauth_token.tres new file mode 100644 index 00000000..0833bd5d --- /dev/null +++ b/addons/twitcher/default_oauth_token.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="OAuthToken" format=3 uid="uid://m7epy882axmp"] + +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="1_byqke"] +[ext_resource type="Script" uid="uid://b52xp7c23ucfk" path="res://addons/twitcher/lib/oOuch/oauth_token.gd" id="2_phbii"] + +[resource] +script = ExtResource("2_phbii") +_identifier = "EditorToken" diff --git a/addons/twitcher/editor/api_generator/twitch_api_generator.gd b/addons/twitcher/editor/api_generator/twitch_api_generator.gd new file mode 100644 index 00000000..4fa4dbed --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_api_generator.gd @@ -0,0 +1,623 @@ +@icon("res://addons/twitcher/assets/api-icon.svg") +@tool +extends Twitcher + +class_name TwitchAPIGenerator + +const suffixes: Array[String] = ["Response", "Body", "Opt"] + +const api_output_path = "res://addons/twitcher/generated/" +const twitch_api_header : String = """@tool +extends Twitcher + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## Interaction with the Twitch REST API. +class_name TwitchAPI + +static var _log: TwitchLogger = TwitchLogger.new("TwitchAPI") + +static var instance: TwitchAPI + +## Maximal tries to reauthrorize before giving up the request. +const MAX_AUTH_ERRORS: int = 3 + +## Called when the API returns unauthenticated mostly cause the accesstoken is expired +signal unauthenticated + +## Called when the API returns 403 means there are permissions / scopes missing +signal unauthorized + +## To authorize against the Twitch API +@export var token: OAuthToken: + set(val): + token = val + update_configuration_warnings() +## OAuth settings needed for client information +@export var oauth_setting: OAuthSetting: + set(val): + oauth_setting = val + update_configuration_warnings() +## URI to the Twitch API +@export var api_host: String = "https://api.twitch.tv/helix" + +## Client to make HTTP requests +var client: BufferedHTTPClient + + +func _ready() -> void: + client = BufferedHTTPClient.new() + client.name = "ApiClient" + add_child(client) + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if token == null: + result.append("Please set a token to use") + if oauth_setting == null: + result.append("Please set the correct oauth settings") + return result + + +func request(path: String, method: int, body: Variant = "", content_type: String = "", error_count: int = 0) -> BufferedHTTPClient.ResponseData: + var header : Dictionary = { + "Authorization": "Bearer %s" % [await token.get_access_token()], + "Client-ID": oauth_setting.client_id + } + if content_type != "": + header["Content-Type"] = content_type + + var request_body: String = "" + if body == null || (body is String && body == ""): + request_body = "" + elif body is Object && body.has_method("to_json"): + request_body = body.to_json() + else: + request_body = JSON.stringify(body) + + var req: BufferedHTTPClient.RequestData = client.request(api_host + path, method, header, request_body) + var res: BufferedHTTPClient.ResponseData = await client.wait_for_request(req) + + # Try to fix Godot TLS Bug + if res.result == 5: + return await retry(req, res, path, method, body, content_type, error_count + 1) + + match res.response_code: + 401: # Token expired / or missing permissions + _log.e("'%s' is unauthorized. It is probably your scopes." % path) + unauthorized.emit() + 403: + _log.i("'%s' is unauthenticated. Refresh token." % path) + unauthenticated.emit() + await token.authorized + return await retry(req, res, path, method, body, content_type, error_count + 1) + return res + + +func retry(request: BufferedHTTPClient.RequestData, + response: BufferedHTTPClient.ResponseData, + path: String, + method: int, + body: Variant = "", + content_type: String = "", + error_count: int = 0) -> BufferedHTTPClient.ResponseData: + if error_count + 1 < MAX_AUTH_ERRORS: + return await request(path, method, body, content_type, error_count + 1) + else: + # Give up the request after trying multiple times and + # return an empty response with correct error code + var empty_response: BufferedHTTPClient.ResponseData = client.empty_response(request) + empty_response.response_code = response.response_code + return empty_response + + +func _handle_error(method_name: String, response: BufferedHTTPClient.ResponseData) -> void: + var error_json: String = response.response_data.get_string_from_utf8() + var error = JSON.parse_string(error_json) + push_error("Problems while calling %s: " % method_name, error["message"]) + _log.d(error_json) + + +## Converts unix timestamp to RFC 3339 (example: 2021-10-27T00:00:00Z) when passed a string uses as is +static func get_rfc_3339_date_format(time: Variant) -> String: + if typeof(time) == TYPE_INT: + var date_time = Time.get_datetime_dict_from_unix_time(time) + return "%s-%02d-%02dT%02d:%02d:%02dZ" % [date_time['year'], date_time['month'], date_time['day'], date_time['hour'], date_time['minute'], date_time['second']] + return str(time) + +""" + +@export var parser: TwitchAPIParser + +var grouped_files: Dictionary[String, Variant] = {} + + +func prepare_component(component: TwitchGenComponent) -> void: + if component._is_root: + var base_name = get_base_name(component._classname) + + # No suffix class lives by its own + if base_name == component._classname: + if grouped_files.has(base_name): + push_error("That file shouldn't exist: %s" % base_name) + component._classname = "Twitch" + component._classname + grouped_files[base_name] = component + else: + var file: GroupedComponent = grouped_files.get(base_name, GroupedComponent.new()) + file.base_name = "Twitch" + base_name + file.components.append(component) + grouped_files[base_name] = file + component._classname = component._classname.trim_prefix(base_name) + component.set_meta("fqdn", file.base_name + "." + component._classname) + var sub_components_to_update: Array[TwitchGenComponent] = component._sub_components.values().duplicate() + for sub_component in sub_components_to_update: + sub_component._classname = component._classname + sub_component._classname + sub_components_to_update.append_array(sub_component._sub_components.values()) + pass + + + +func generate_api() -> void: + for component: Variant in parser.components: + prepare_component(component) + + # Generate TwitchAPI + var twitch_api_code = twitch_api_header + for method: TwitchGenMethod in parser.methods: + twitch_api_code += method_code(method) + write_output_file(api_output_path + "twitch_api.gd", twitch_api_code) + + # Generate Components + for component: Variant in grouped_files.values(): + var code = "" + if component is GroupedComponent: + code = group_code(component) + else: + code = component_code(component, 0) + write_output_file(api_output_path + component.get_filename(), code) + + print("API regenerated you can find it under: %s" % api_output_path) + + +class GroupedComponent extends RefCounted: + var base_name: String + var prefix: String + var components: Array[TwitchGenComponent] = [] + + + func _update_base_name(val: String) -> void: + base_name = val + + + func get_filename() -> String: + return base_name.to_snake_case() + ".gd" + + +func group_code(group: GroupedComponent) -> String: + var code = """@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name {name} + """.format({"name": group.base_name}) + for component in group.components: + component._is_root = false + code += "\n\n" + code += component_code(component, 1) + return code + +#region Field Code Generation + +func field_declaration(field: TwitchGenField) -> String: + var type = get_type(field._type, field._is_array) + return """ +## {description} +@export var {name}: {type}: + set(val): + {name} = val + track_data(&"{name}", val)\n""".format({ + "name": field._name, + "description": ident(field._description, 0, "## "), + "type": type + }) + + +#endregion + +#region Parameter Code Generation + +#func get_code() -> String: + #if _name == "broadcaster_id": + #var default_value = "default_broadcaster_login" if _type == "String" else "[default_broadcaster_login]" + #return "%s: %s = %s" % [_name, get_type(), default_value] + #return "%s: %s" % [_name, get_type()] + +#endregion + +#region Method Code Generation + +func parameter_doc(method: TwitchGenMethod) -> String: + if method._required_parameters.is_empty(): + return "## [no required query parameters to describe]" + var doc : String = "" + for parameter: TwitchGenParameter in method._required_parameters: + doc += "## {name} - {documentation} \n".format({ + 'name': parameter._name, + 'documentation': ident(parameter._description, 0, "## ") + }) + return doc.rstrip("\n") + + +func parameter_array(method: TwitchGenMethod, with_type: bool = false, fully_qualified: bool = false) -> Array[String]: + var parameters : Array[String] = [] + if method._contains_body: parameters.append(get_parameter("body", method._body_type, false, with_type, fully_qualified)) + if method._contains_optional: parameters.append(get_parameter("opt", method.get_optional_type(), false, with_type, fully_qualified)) + + method._parameters.sort_custom(TwitchGenParameter.sort) + for parameter: TwitchGenParameter in method._required_parameters: + parameters.append(get_parameter(parameter._name, parameter._type, parameter._is_array, with_type, fully_qualified)) + return parameters + + +func method_parameter(method: TwitchGenMethod, with_type: bool = false, fully_qualified: bool = false) -> String: + return ", ".join(parameter_array(method, with_type, fully_qualified)) + + +func path_code(method: TwitchGenMethod) -> String: + var body_code : String = "var path = \"%s?\"\n" % method._path + + if method._contains_optional: + body_code += "var optionals: Dictionary[StringName, Variant] = {}\n" + body_code += "if opt != null: optionals = opt.to_dict()\n" + + for parameter: TwitchGenParameter in method._parameters: + if parameter._required: + body_code += parameter_path_code(parameter) + "\n" + else: + body_code += "if optionals.has(\"%s\"):\n" % parameter._name + body_code += "\t%s\n" % ident(parameter_path_code(parameter, "optionals."), 1) + return body_code + + +func parameter_path_code(parameter: TwitchGenParameter, prefix: String = "") -> String: + var body: String + if parameter._is_time: + body = "path += \"{key}=\" + get_rfc_3339_date_format({value}) + \"&\"" + + elif parameter._is_array: + body = """ +for param in {value}: + path += "{key}=" + str(param) + "&" """.trim_prefix("\n\t") + else: + body = "path += \"{key}=\" + str({value}) + \"&\"" + + return body.format({ + 'value': prefix + parameter._name, + 'key': parameter._name + }) + + ## Exceptional method cause twitch api is not uniform +func paging_code_stream_schedule() -> String: + return """ +if parsed_result.data.pagination != null: + opt.after = parsed_result.data.pagination.cursor + parsed_result.data._next_page = get_channel_stream_schedule.bind(opt, broadcaster_id)\n""" + + +func paging_code(method: TwitchGenMethod) -> String: + if method._name == "get_channel_stream_schedule": + return paging_code_stream_schedule() + + var code: String = "" + code += "if parsed_result.pagination != null:\n" + var after_parameter: TwitchGenParameter = method.get_parameter_by_name("after") + var result_component: TwitchGenComponent = get_component(method._result_type) + var pagination_parameter: TwitchGenField = result_component.get_field_by_name("pagination") + if pagination_parameter == null: + print("Check %s paging without paging?" % method._name) + pass + elif pagination_parameter._type == "String": + code += "\tvar cursor: String = parsed_result.pagination\n" + else: + code += "\tvar cursor: String = parsed_result.pagination.cursor\n" + if after_parameter._required: + code += "\t{parameter} = cursor\n" + else: + code += "\tif not opt: opt = {opt_type}.new()\n" + code += "\topt.{parameter} = cursor\n" + code += "\tparsed_result._next_page = {name}.bind({parameters})\n" + + var opt_parameter: String = get_type(method.get_optional_type(), false, true) + return code.format({ + "parameter": after_parameter._name, + "name": method._name, + "parameters": method_parameter(method), + "opt_type": opt_parameter + }) + + +func method_code(method: TwitchGenMethod) -> String: + print("GENERATE METHOD ", method._name) + + var result_type = get_type(method._result_type, false, true) + + var template = """ + +## {summary} +## +{parameter_doc} +## +## {doc_url} +func {name}({parameters}) -> {result_type}: + {path_code} + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_{method}, {body_variable}, "{content_type}") +""" + + if result_type == "BufferedHTTPClient.ResponseData": + template += """ + if response.response_code >= 400: + _handle_error("{name}", response) + return response +""" + else: + template += """ + var result: Variant = {} + if response.response_code >= 400: + _handle_error("{name}", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: {result_type} = {result_type}.from_json(result) + parsed_result.response = response + {paging_code} + return parsed_result +""" + + return template.format({ + "summary": method._summary, + "parameter_doc": parameter_doc(method), + "doc_url": method._doc_url, + "name": method._name, + "parameters": method_parameter(method, true, true), + "result_type": get_type(method._result_type, false, true), + "path_code": ident(path_code(method), 1), + "content_type": get_type(method._content_type, false, true), + "method": method._http_verb.to_upper(), + "body_variable": "body" if method._contains_body else "\"\"", + "paging_code": ident(paging_code(method), 1) if method._has_paging else "" + }) + + +#endregion + +#region Component Code Generation + + +func component_code(component: TwitchGenComponent, level: int = 0) -> String: + var code: String = "" + if component._is_root: + code += """@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## {description} +## {ref} +class_name {classname} + """ + else: + code = """ +## {description} +## {ref} +class {classname} extends TwitchData: +""" + var class_code : String = "" + for field: TwitchGenField in component._fields: + class_code += field_declaration(field) + + if component._is_response: + class_code += "var response: BufferedHTTPClient.ResponseData" + class_code += "\n\n" + class_code += create_code(component) + "\n\n" + class_code += from_json_code(component) + + if component._has_paging: + class_code += "\n\n" + iter_code(component) + + var sub_component_code: String + for sub_component in component._sub_components.values(): + sub_component_code += "\n\n" + component_code(sub_component, 1) + + return code.format({ + "description": ident(component._description, 0, "## "), + "classname": component._classname, + "ref": component._ref + }) + ident(class_code, level) + sub_component_code + + +func create_code(component: TwitchGenComponent) -> String: + var parameters: Array[String] = [] + for field in component._fields: + if field._is_required: + parameters.append("_" + get_parameter(field._name, field._type, field._is_array)) + + var variable_name = component._classname.to_snake_case() + var code : String = """ +## Constructor with all required fields. +static func create({parameters}) -> {classname}: + var {variablename}: {classname} = {classname}.new()\n""".format({ + "parameters": ", ".join(parameters), + "classname": component._classname, + "variablename": variable_name + }) + + for field in component._fields: + if field._is_required: + code += "\t{classname}.{name} = _{name}\n".format({ + "name": field._name, + "classname": variable_name + }) + + code += "\treturn %s" % variable_name + return code + + +func from_json_code(component: TwitchGenComponent) -> String: + var code : String = """ +static func from_json(d: Dictionary) -> {classname}: + var result: {classname} = {classname}.new() +""".format({"classname": component._classname}) + for field: TwitchGenField in component._fields: + code += "\tif d.get(\"{name}\", null) != null:\n" + if field._is_typed_array: + code += """ + for value in d["{name}"]: + result.{name}.append({type}.from_json(value))\n""".lstrip("\n") + elif field._is_array: + code += """ + for value in d["{name}"]: + result.{name}.append(value)\n""".lstrip("\n") + elif field._is_sub_class: + code += "\t\tresult.{name} = {type}.from_json(d[\"{name}\"])\n" + else: + code += "\t\tresult.{name} = d[\"{name}\"]\n" + code = code.format({ + "name": field._name, + "type": get_type(field._type, false) + }) + code += "\treturn result\n" + + return code + + +func iter_code(component: TwitchGenComponent) -> String: + var data_variable_name: String = "data" + var path_to_data: String = "" + if component._ref == "#/components/schemas/GetChannelStreamScheduleResponse/Data": + data_variable_name = "segments" + path_to_data = "data." + + var code: String + if component._ref == "#/components/schemas/GetExtensionLiveChannelsResponse": + code += """ +func _has_pagination() -> bool: + if pagination == null || pagination == "": return false + return true +""" + else: + code += """ +func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true +""" + + code += """ +var _next_page: Callable +var _cur_iter: int = 0 + + +func next_page() -> {response_type}: + var response: {response_type} = await _next_page.call() + _cur_iter = 0 + _next_page = response.{path_to_data}_next_page +{copy_code} + return response + + +func _iter_init(iter: Array) -> bool: + if {data_variable_name}.is_empty(): return false + iter[0] = {data_variable_name}[0] + _cur_iter = 1 + return true + + +func _iter_next(iter: Array) -> bool: + if {data_variable_name}.size() > _cur_iter: + iter[0] = {data_variable_name}[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + +func _iter_get(iter: Variant) -> Variant: + if {data_variable_name}.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter""" + var copy_code: String + for field in component._fields: + copy_code += "\t{_name} = response.{path_to_data}{_name}\n".format(field) + + return code.format({ + "data_variable_name": data_variable_name, + "copy_code": copy_code, + "path_to_data": path_to_data, + "response_type": component.get_root_classname() + }) +#endregion + +#region Utils + + +func get_type(type: String, is_array: bool = false, full_qualified: bool = false) -> String: + var result_type : String = "" + if type.begins_with("#"): + var component: TwitchGenComponent = parser.get_component_by_ref(type) + result_type = component._classname + if full_qualified and component.has_meta("fqdn"): + result_type = component.get_meta("fqdn") + else: + result_type = type + return result_type if not is_array else "Array[%s]" % result_type + + +func get_component(type: String) -> TwitchGenComponent: + if type.begins_with("#"): + return parser.get_component_by_ref(type) + else: + return null + +func ident(code: String, level: int, padding: String = "") -> String: + return code.replace("\n", "\n" + "\t".repeat(level) + padding) + + +# Writes the processed content to the output file. +func write_output_file(file_output: String, content: String) -> void: + var file = FileAccess.open(file_output, FileAccess.WRITE); + if file == null: + var error_message = error_string(FileAccess.get_open_error()); + push_error("Failed to open output file: %s\n%s" % [file_output, error_message]) + return + file.store_string(content) + file.flush() + file.close() + + +func get_base_name(file: String) -> String: + var new_file: String = file + for suffix: String in suffixes: + new_file = new_file.trim_suffix(suffix) + return new_file + + +func get_parameter(title: String, type: String, is_array = false, with_type: bool = true, fully_qualified: bool = false) -> String: + if with_type: + return "{name}: {type}".format({ + "name": title, + "type": get_type(type, is_array, fully_qualified) + }) + else: + return title + +#endregion diff --git a/addons/twitcher/editor/api_generator/twitch_api_generator.gd.uid b/addons/twitcher/editor/api_generator/twitch_api_generator.gd.uid new file mode 100644 index 00000000..3f6eedac --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_api_generator.gd.uid @@ -0,0 +1 @@ +uid://cetl2un34bjb1 diff --git a/addons/twitcher/editor/api_generator/twitch_api_parser.gd b/addons/twitcher/editor/api_generator/twitch_api_parser.gd new file mode 100644 index 00000000..424d6ad6 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_api_parser.gd @@ -0,0 +1,246 @@ +@icon("res://addons/twitcher/assets/api-icon.svg") +@tool +extends Twitcher + +class_name TwitchAPIParser + +const SWAGGER_API: String = "https://raw.githubusercontent.com/DmitryScaletta/twitch-api-swagger/refs/heads/main/openapi.json" + +var definition: Dictionary = {} +var component_map: Dictionary[String, TwitchGenComponent] = {} +var components: Array[TwitchGenComponent] = [] +var methods: Array[TwitchGenMethod] = [] + +var client: BufferedHTTPClient = BufferedHTTPClient.new() + +signal component_added(component: TwitchGenComponent) +signal method_added(method: TwitchGenMethod) + + +class ComponentRepo extends RefCounted: + var _component: TwitchGenComponent + var _component_map: Dictionary[String, TwitchGenComponent] + + + func get_comp(component_name: String) -> TwitchGenComponent: + var component: TwitchGenComponent = _component.get_component(component_name) + if component != null: return component + return _component_map.get(component_name) + + + func _init(component: TwitchGenComponent, component_map: Dictionary[String, TwitchGenComponent]) -> void: + _component = component + _component_map = component_map + + +func _ready() -> void: + client.name = "APIGeneratorClient" + + +func parse_api() -> void: + print("start generating API") + if definition == {}: + print("load Twitch definition") + definition = await _load_swagger_definition() + + _parsing_components() + _parsing_paths() + + +func _load_swagger_definition() -> Dictionary: + add_child(client) + client.max_error_count = 3 + var request = client.request(SWAGGER_API, HTTPClient.METHOD_GET, {}, "") + var response_data = await client.wait_for_request(request) + + if response_data.error: + printerr("Cant generate API") + return {} + var response_str = response_data.response_data.get_string_from_utf8() + var response = JSON.parse_string(response_str) + remove_child(client) + return response + + +func _parsing_components() -> void: + var schemas = definition["components"]["schemas"] + for schema_name in schemas: + var schema: Dictionary = schemas[schema_name] + if schema["type"] != "object": + printerr("Not an object") + continue + + var ref = "#/components/schemas/" + schema_name + var component = TwitchGenComponent.new(schema_name, ref) + component._is_root = true + _parse_properties(component, schema) + _add_component(ref, component) + + +func _parse_properties(component: TwitchGenComponent, schema: Dictionary) -> void: + var properties = schema["properties"] + for property_name: String in properties: + var property: Dictionary = properties[property_name] + var field: TwitchGenField = TwitchGenField.new() + field._name = property_name + field._description = property.get("description", "") + field._type = _get_param_type(property) + + var classname: String = property_name.capitalize().replace(" ", "") + + + if property.has("properties"): + var sub_component: TwitchGenComponent = _add_sub_component(classname, field._description, component, property) + field._type = sub_component._ref + + ## Arrays that has custom types + elif property.get("type", "") == "array": + field._is_array = true + field._is_sub_class = false + var items = property.get("items", {}) + if items.has("$ref"): + field._type = items.get("$ref") + elif items.has("properties"): + var sub_component: TwitchGenComponent = _add_sub_component(classname, field._description, component, items) + field._type = sub_component._ref + + component.add_field(field) + + var requires: Array = schema.get("required", []) + for required_field: String in requires: + var field: TwitchGenField = component.get_field_by_name(required_field) + field._is_required = true + + +func _add_sub_component(classname: String, description: String, parent_component: TwitchGenComponent, properties: Dictionary) -> TwitchGenComponent: + var ref: String = parent_component._ref + "/" + classname + var sub_component = TwitchGenComponent.new(classname, ref) + sub_component._description = description + _parse_properties(sub_component, properties) + parent_component.add_component(sub_component) + _add_component(ref, sub_component) + return sub_component + + +func _parsing_paths() -> void: + var paths = definition.get("paths", {}) + for path in paths: + var method_specs = paths[path] + for http_verb: String in method_specs: + var method_spec: Dictionary = method_specs[http_verb] as Dictionary + var method: TwitchGenMethod = _parse_method(http_verb, method_spec) + method._path = path + if method._contains_optional: + var component : TwitchGenComponent = method.get_optional_component() + _add_component(component._ref, component) + methods.append(method) + method_added.emit(method) + + +func _parse_method(http_verb: String, method_spec: Dictionary) -> TwitchGenMethod: + var method: TwitchGenMethod = TwitchGenMethod.new() + method._http_verb = http_verb + method._name = method_spec.get("operationId", "method_" + http_verb).replace("-", "_") + method._summary = method_spec.get("summary", "No summary provided.") + method._description = method_spec.get("description", "No description provided.") + method._doc_url = method_spec.get("externalDocs", {}).get("url", "No link provided") + _parse_parameters(method, method_spec) + + # Body Type + if method_spec.has("requestBody"): + method._body_type = "Dictionary" + var ref = method_spec.get("requestBody").get("content", {}).get("application/json", {}).get("schema", {}).get("$ref", "") + if ref != "": + method._body_type = ref + + # Result Type + method._result_type = "BufferedHTTPClient.ResponseData" + var responses = method_spec.get("responses", {}) + if responses.has("200") || responses.has("202"): + var content: Dictionary = {} + if responses.has("200"): + content = responses["200"].get("content", {}) + elif content == {}: + content = responses["202"].get("content", {}) + + # Assuming the successful response is a JSON object + method._result_type = "Dictionary" + + # Special case for /schedule/icalendar + if content.has("text/calendar"): + method._result_type = "BufferedHTTPClient.ResponseData" + + + # Try to resolve the component references + var ref = content.get("application/json", {}).get("schema", {}).get("$ref", "") + if ref != "": + method._result_type = ref + + # Content Type + if method_spec.has("requestBody"): + var requestBody = method_spec.get("requestBody") + var content = requestBody.get("content") + method._content_type = content.keys()[0] + elif http_verb == "POST": + method._content_type = "application/x-www-form-urlencoded" + return method + + +func _parse_parameters(method: TwitchGenMethod, method_spec: Dictionary) -> void: + var parameter_specs = method_spec.get("parameters", []) + for parameter_spec in parameter_specs: + var parameter: TwitchGenParameter = TwitchGenParameter.new() + var schema = parameter_spec["schema"] + parameter._name = parameter_spec.get("name", "") + parameter._description = parameter_spec.get("description", "") + parameter._type = _get_param_type(schema) + parameter._required = parameter_spec.get("required", false) + parameter._is_time = schema.get("format", "") == "date-time" + parameter._is_array = schema.get("type", "") == "array" + method.add_parameter(parameter) + + +func _add_component(ref: String, component: TwitchGenComponent) -> void: + components.append(component) + component_map[ref] = component + + +func get_component_by_ref(ref: String) -> TwitchGenComponent: + return component_map[ref] + + +func _get_param_type(schema: Dictionary) -> String: + if schema.has("$ref"): + return schema["$ref"] + + if not schema.has("type"): + return "Variant" # Maybe ugly + + var type: String = schema["type"].to_lower() + var format = schema.get("format", "") + match type: + "object": + if schema.has("additinalProperties"): + return _get_param_type(schema["additinalProperties"]) + return "Dictionary" + "string": + # Why did I do this in the first place? + # Lets disable and see if problems appear + #if format == "date-time": + # return "Variant" + return "String" + "integer": + return "int" + "number": + return "float" if format == "float" else "int" + "boolean", "bool": + return "bool" + "array": + var ref: String = schema["items"].get("$ref", "") + if schema["items"].get("type", "") == "string": + return "String" + elif ref != "": + return ref + else: + return "Variant" + _: return "Variant" diff --git a/addons/twitcher/editor/api_generator/twitch_api_parser.gd.uid b/addons/twitcher/editor/api_generator/twitch_api_parser.gd.uid new file mode 100644 index 00000000..6eecd607 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_api_parser.gd.uid @@ -0,0 +1 @@ +uid://o7q04krfh33l diff --git a/addons/twitcher/editor/api_generator/twitch_gen.gd b/addons/twitcher/editor/api_generator/twitch_gen.gd new file mode 100644 index 00000000..874bc07f --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen.gd @@ -0,0 +1,3 @@ +extends RefCounted + +class_name TwitchGen diff --git a/addons/twitcher/editor/api_generator/twitch_gen.gd.uid b/addons/twitcher/editor/api_generator/twitch_gen.gd.uid new file mode 100644 index 00000000..fd8412ff --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen.gd.uid @@ -0,0 +1 @@ +uid://cvtstbwwwf7gf diff --git a/addons/twitcher/editor/api_generator/twitch_gen_component.gd b/addons/twitcher/editor/api_generator/twitch_gen_component.gd new file mode 100644 index 00000000..8da31e95 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_component.gd @@ -0,0 +1,62 @@ +@tool +extends TwitchGen + +class_name TwitchGenComponent + +var _classname: String +var _ref: String +var _description: String +var _fields: Array[TwitchGenField] = [] +var _field_map: Dictionary[String, TwitchGenField] = {} +var _parent_component: TwitchGenComponent +var _sub_components: Dictionary[String, TwitchGenComponent] = {} +var _is_root: bool +var _is_response: bool: + get(): return _classname.contains("Response") + +var _has_paging: bool +var _filename: String: + get(): return _classname.to_snake_case() + ".gd" + + +func _init(classname: String, ref: String) -> void: + _classname = _sanatize_classname(classname) + _ref = ref + + +func add_field(field: TwitchGenField) -> void: + _fields.append(field) + _field_map[field._name] = field + if field._name == "pagination": + _has_paging = true + + +func get_field_by_name(field_name: String) -> TwitchGenField: + return _field_map.get(field_name, null) + + +func get_root_classname() -> String: + var parent: TwitchGenComponent = self + while parent._parent_component != null: + parent = parent._parent_component + return parent._classname + + +func get_filename() -> String: + return get_root_classname().to_snake_case() + ".gd" + + +func _sanatize_classname(val: String) -> String: + match val: + "Image": return "TwitchImage" + "Panel": return "TwitchPanel" + _: return val + + +func get_component(component_name: String) -> TwitchGenComponent: + return _sub_components.get(component_name) + + +func add_component(sub_component: TwitchGenComponent) -> void: + _sub_components[sub_component._classname] = sub_component + sub_component._parent_component = self diff --git a/addons/twitcher/editor/api_generator/twitch_gen_component.gd.uid b/addons/twitcher/editor/api_generator/twitch_gen_component.gd.uid new file mode 100644 index 00000000..3fac53cf --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_component.gd.uid @@ -0,0 +1 @@ +uid://cttaojg743q5y diff --git a/addons/twitcher/editor/api_generator/twitch_gen_field.gd b/addons/twitcher/editor/api_generator/twitch_gen_field.gd new file mode 100644 index 00000000..6dd313e8 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_field.gd @@ -0,0 +1,31 @@ +@tool +extends TwitchGen + +class_name TwitchGenField + +var _name: String: + set = _update_name +var _description: String +var _type: String +var _is_required: bool +var _is_sub_class: bool: + get(): return _type.begins_with("#") +var _is_array: bool +var _is_typed_array: bool: + get(): return _is_array && _type.begins_with("#") + + +## Couple of names from the Twitch API are messed up like keywords for godot or numbers +func _update_name(val: String) -> void: + match val: + "animated": _name = "animated_format" + "static": _name = "static_format" + "1": _name = "_1" + "2": _name = "_2" + "3": _name = "_3" + "4": _name = "_4" + "1.5": _name = "_1_5" + "100x100": _name = "_100x100" + "24x24": _name = "_24x24" + "300x200": _name = "_300x200" + _: _name = val diff --git a/addons/twitcher/editor/api_generator/twitch_gen_field.gd.uid b/addons/twitcher/editor/api_generator/twitch_gen_field.gd.uid new file mode 100644 index 00000000..7424fef7 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_field.gd.uid @@ -0,0 +1 @@ +uid://dgx1w74garjdh diff --git a/addons/twitcher/editor/api_generator/twitch_gen_method.gd b/addons/twitcher/editor/api_generator/twitch_gen_method.gd new file mode 100644 index 00000000..a64f5d5a --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_method.gd @@ -0,0 +1,59 @@ +@tool +extends TwitchGen + +class_name TwitchGenMethod +var _http_verb: String +var _name: String +var _summary: String +var _description: String +var _path: String +var _doc_url: String +var _parameters: Array[TwitchGenParameter] = [] +var _parameter_map: Dictionary[String, TwitchGenParameter] = {} +var _required_parameters: Array[TwitchGenParameter]: + get(): return _parameters.filter(func(p): return p._required) +var _optional_parameters: Array[TwitchGenParameter]: + get(): return _parameters.filter(func(p): return not p._required) +var _body_type: String +var _result_type: String +var _content_type: String +var _has_paging: bool +var _contains_optional: bool +var _contains_body: bool: + get(): return _body_type != null and _body_type != "" + + +func add_parameter(parameter: TwitchGenParameter) -> void: + _parameters.append(parameter) + _contains_optional = _contains_optional || not parameter._required + _parameter_map[parameter._name] = parameter + if parameter._name == "after": + _has_paging = true + + +func get_parameter_by_name(name: String) -> TwitchGenParameter: + return _parameter_map.get(name) + + +func get_optional_classname() -> String: + return _name.capitalize().replace(" ", "") + "Opt" + + +func get_optional_type() -> String: + return "#/components/schemas/" + get_optional_classname() + + +func get_optional_component() -> TwitchGenComponent: + var component = TwitchGenComponent.new(get_optional_classname(), get_optional_type()) + component._description = "All optional parameters for TwitchAPI.%s" % _name + component._is_root = true + for parameter: TwitchGenParameter in _optional_parameters: + var field = TwitchGenField.new() + field._name = parameter._name + field._type = parameter._type + field._description = parameter._description + field._is_required = false + field._is_array = parameter._is_array + component.add_field(field) + + return component diff --git a/addons/twitcher/editor/api_generator/twitch_gen_method.gd.uid b/addons/twitcher/editor/api_generator/twitch_gen_method.gd.uid new file mode 100644 index 00000000..ab432ec3 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_method.gd.uid @@ -0,0 +1 @@ +uid://dkwhf6s838d20 diff --git a/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd b/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd new file mode 100644 index 00000000..656596f9 --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd @@ -0,0 +1,22 @@ +@tool +extends TwitchGen + +class_name TwitchGenParameter +var _name: String +var _description: String +var _required: bool +var _type: String +var _is_time: bool +var _is_array: bool + +static func sort(p1: TwitchGenParameter, p2: TwitchGenParameter) -> bool: + if p1._name == "broadcaster_id": + return false + if p2._name == "broadcaster_id": + return true + if p1._required && not p2._required: + return true + if not p1._required && p2._required: + return false + return p1._name < p2._name + diff --git a/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd.uid b/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd.uid new file mode 100644 index 00000000..4d35c02b --- /dev/null +++ b/addons/twitcher/editor/api_generator/twitch_gen_parameter.gd.uid @@ -0,0 +1 @@ +uid://1ptmbsygcfyt diff --git a/addons/twitcher/editor/inspector/twitch_authorize_editor.gd b/addons/twitcher/editor/inspector/twitch_authorize_editor.gd new file mode 100644 index 00000000..7037fabe --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_authorize_editor.gd @@ -0,0 +1,26 @@ +@tool +extends VBoxContainer + +## Helper class to get an UI for authorize the Editor + +const TEST_CREDENTIALS: PackedScene = preload("res://addons/twitcher/editor/setup/test_credentials.tscn") +const TestCredentials = preload("res://addons/twitcher/editor/setup/test_credentials.gd") + +signal authorized + +func _init(name_of_object_to_edit: String) -> void: + var info_label: Label = Label.new() + info_label.text = "Authorize editor to have a custom inspector for the '%s'." % name_of_object_to_edit.capitalize() + info_label.label_settings = LabelSettings.new() + info_label.label_settings.font_size = 13 + info_label.label_settings.font_color = Color.AQUA + info_label.autowrap_mode = TextServer.AUTOWRAP_WORD + info_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + var authorize_editor: TestCredentials = TEST_CREDENTIALS.instantiate() + authorize_editor.text = "Authorize Editor" + authorize_editor.authorized.connect(func(): authorized.emit()) + + add_child(info_label) + add_child(authorize_editor) + size_flags_horizontal = Control.SIZE_EXPAND_FILL diff --git a/addons/twitcher/editor/inspector/twitch_authorize_editor.gd.uid b/addons/twitcher/editor/inspector/twitch_authorize_editor.gd.uid new file mode 100644 index 00000000..733bfbc7 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_authorize_editor.gd.uid @@ -0,0 +1 @@ +uid://t5rnk4ntnf diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd b/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd new file mode 100644 index 00000000..57b02dff --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd @@ -0,0 +1,37 @@ +extends EditorInspectorPlugin + +const EventsubConfigProperty = preload("res://addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd") + +func _can_handle(object: Object) -> bool: + return object is TwitchEventsubConfig + + +func _parse_property(object: Object, type: Variant.Type, name: String, \ + hint_type: PropertyHint, hint_string: String, usage_flags: int, \ + wide: bool) -> bool: + + if name == &"condition": + add_property_editor("condition", EventsubConfigProperty.new(), true) + return true + if name == &"type": + add_property_editor("type", ToDocs.new(), true, "Documentation") + return false + + +class ToDocs extends EditorProperty: + const EXT_LINK = preload("res://addons/twitcher/assets/ext-link.svg") + + var docs = Button.new() + + + func _init() -> void: + docs.text = "To dev.twitch.tv" + docs.icon = EXT_LINK + docs.pressed.connect(_on_to_docs) + add_child(docs) + add_focusable(docs) + + + func _on_to_docs() -> void: + var eventsub_config: TwitchEventsubConfig = get_edited_object() + OS.shell_open(eventsub_config.definition.documentation_link) diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd.uid new file mode 100644 index 00000000..965aa5f0 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd.uid @@ -0,0 +1 @@ +uid://c1afm3xjonwxr diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd b/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd new file mode 100644 index 00000000..e72d756f --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd @@ -0,0 +1,104 @@ +extends EditorProperty + +const USER_CONVERTER = preload("res://addons/twitcher/editor/inspector/user_converter.tscn") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + +var _container: Node = GridContainer.new() +var _current_type: TwitchEventsubDefinition.Type + +func _init(): + _container.columns = 2 + add_child(_container) + set_bottom_editor(_container) + + +func _cleanup_unused_meta() -> void: + var eventsub_config: TwitchEventsubConfig = get_edited_object(); + if eventsub_config == null: return + + var conditions: Array[StringName] = eventsub_config.definition.conditions + var unused_conditions: Array[StringName] = eventsub_config.get_meta_list() \ + .filter(func(cond: StringName): + var is_user_property = cond.ends_with("_user") + var condition_not_found = conditions.find(cond.trim_suffix("_user")) == -1 + return is_user_property && condition_not_found) + + for unused_condition: StringName in unused_conditions: + eventsub_config.remove_meta(unused_condition) + + +func _update_property() -> void: + var eventsub_config: TwitchEventsubConfig = get_edited_object(); + + _cleanup_unused_meta() + _clean_conditions() + _create_conditions() + + +func _clean_conditions() -> void: + for node in _container.get_children(): + node.queue_free() + + +func _create_conditions() -> void: + var eventsub_config: TwitchEventsubConfig = get_edited_object(); + if eventsub_config == null || eventsub_config.get_class() == &"EditorDebuggerRemoteObject": return + + for condition_name: StringName in eventsub_config.definition.conditions: + var condition_value = eventsub_config.condition.get_or_add(condition_name, "") + _create_condition_title(condition_name) + + var editor_token = TwitchEditorSettings.editor_oauth_token + if condition_name.to_lower().ends_with("user_id") && editor_token.is_token_valid(): + _create_editor_supported_input(eventsub_config, condition_name, condition_value) + else: + _create_manual_input(condition_name, condition_value) + + +func _create_condition_title(condition_name: String) -> void: + var condition_title = Label.new() + condition_title.text = condition_name.capitalize() + _container.add_child(condition_title) + + +func _create_editor_supported_input(eventsub_config: TwitchEventsubConfig, \ + condition_name: String, \ + condition_value: String) -> void: + var user_converter = USER_CONVERTER.instantiate() + user_converter.changed.connect(_on_changed_user.bind(eventsub_config, condition_name)) + _container.add_child(user_converter) + + if eventsub_config.has_meta(condition_name + "_user"): + var user = eventsub_config.get_meta(condition_name + "_user") + user_converter.update_user(user) + elif condition_value != "": + user_converter.user_id = condition_value + user_converter.reload() + + +func _create_manual_input(condition_name: String, condition_value: String) -> void: + var input = LineEdit.new() + input.text_submitted.connect(_on_change_text.bind(condition_name, input)) + input.focus_exited.connect(_on_change_text.bind("", condition_name, input)) + input.text = condition_value + input.size_flags_horizontal = Control.SIZE_EXPAND_FILL + _container.add_child(input) + + +func _on_changed_user(user: TwitchUser, \ + eventsub_config: TwitchEventsubConfig, \ + condition_name: StringName) -> void: + var conditions: Dictionary = eventsub_config.condition.duplicate() + if user == null: + conditions[condition_name] = "" + eventsub_config.remove_meta(condition_name + "_user") + else: + conditions[condition_name] = user.id + eventsub_config.set_meta(condition_name + "_user", user) + eventsub_config.condition = conditions + emit_changed(&"condition", eventsub_config.condition) + +func _on_change_text(new_text: String, condition_name: StringName, input: LineEdit) -> void: + var eventsub_config: TwitchEventsubConfig = get_edited_object(); + eventsub_config.condition[condition_name] = input.text + #emit_changed(&"condition", eventsub_config.condition) diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd.uid b/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd.uid new file mode 100644 index 00000000..909afb6a --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_config_property.gd.uid @@ -0,0 +1 @@ +uid://drv4gmn8akxf2 diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd b/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd new file mode 100644 index 00000000..c15c2183 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd @@ -0,0 +1,83 @@ +extends EditorInspectorPlugin + + +func _can_handle(object: Object) -> bool: + return object is TwitchEventsub || object is TwitchService + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: + if name == &"scopes" && object.get_class() != &"EditorDebuggerRemoteObject": + if (object is TwitchService && object.eventsub != null) || object is TwitchEventsub: + add_property_editor(&"scope_validation", ScopeValidation.new(), true, "Scope Validation") + return false + + +class ScopeValidation extends EditorProperty: + const WARNING_LABEL_SETTINGS = preload("uid://cng881nsuud80") + const INFO_LABEL_SETTINGS = preload("uid://d12dapnv7b00n") + var _warning_label: Label = Label.new(); + var _apply_scopes: Button = Button.new(); + var _needed_scopes: Dictionary = {} + var container: Control = VBoxContainer.new() + + + func _init(): + _warning_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _warning_label.text = "Press validate to check if scopes maybe are missing." + + var validate_button = Button.new(); + validate_button.text = "Validate"; + validate_button.tooltip_text = "Checks the scopes of the subscriptions " \ + + " if they match the defined scopes in the scope " \ + + " property"; + validate_button.pressed.connect(_on_validate_scopes); + + _apply_scopes.text = "Apply Scopes"; + _apply_scopes.tooltip_text = "Apply Scopes to the scope resource in " \ + + " this TwitchEventsub. It maybe not needed depending on the " \ + + " Subscription. Please check the documentation if there is a logical " \ + + " condition and apply the scopes accordingly."; + _apply_scopes.pressed.connect(_on_apply_scopes); + + add_child(validate_button) + container.add_child(_warning_label) + add_child(container) + set_bottom_editor(container) + + + func _on_apply_scopes() -> void: + var scopes = get_edited_object().scopes; + var scopes_to_add: Array[StringName] = []; + for scope in _needed_scopes.values(): + scopes_to_add.append(scope); + scopes.add_scopes(scopes_to_add); + _clear_warning(); + + + func _on_validate_scopes() -> void: + var scopes = get_edited_object().scopes; + var subscriptions = get_edited_object().get_subscriptions(); + + _needed_scopes.clear() + for subscription: TwitchEventsubConfig in subscriptions: + if subscription == null: continue + for scope in subscription.definition.scopes: + _needed_scopes[scope] = scope + + for scope in scopes.used_scopes: + _needed_scopes.erase(scope) + + if !_needed_scopes.is_empty(): + if _apply_scopes.get_parent() == null: container.add_child(_apply_scopes) + _warning_label.label_settings = WARNING_LABEL_SETTINGS + var needed_scopes = ", ".join(_needed_scopes.values()) + _warning_label.text = "You may miss scopes please check documentation if you need to add: %s" % needed_scopes; + else: + _clear_warning() + + + func _clear_warning() -> void: + _warning_label.text = "Scopes seems to be OK for this EventSub." + _warning_label.label_settings = INFO_LABEL_SETTINGS + if _apply_scopes.get_parent() != null: + container.remove_child(_apply_scopes) diff --git a/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd.uid new file mode 100644 index 00000000..d486e886 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd.uid @@ -0,0 +1 @@ +uid://cuo2g5oib1cf3 diff --git a/addons/twitcher/editor/inspector/twitch_icon.gd b/addons/twitcher/editor/inspector/twitch_icon.gd new file mode 100644 index 00000000..0be169d0 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_icon.gd @@ -0,0 +1,28 @@ +@tool +extends Button + +@onready var _image: TextureRect = %Image +@onready var _title: Label = %Title + +@export var texture: Texture2D: + set = _update_texture + +@export var title: String + + +func _ready() -> void: + _update_texture(texture) + _title.text = title + + +func _pressed() -> void: + OS.shell_open("https://dashboard.twitch.tv/viewer-rewards/channel-points/rewards") + + +func _update_texture(val: Texture2D) -> void: + texture = val + if not is_node_ready(): return + if val: + _image.texture = val + else: + _image.texture = GradientTexture1D.new() diff --git a/addons/twitcher/editor/inspector/twitch_icon.gd.uid b/addons/twitcher/editor/inspector/twitch_icon.gd.uid new file mode 100644 index 00000000..358dda5e --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_icon.gd.uid @@ -0,0 +1 @@ +uid://b7lnsgy0s2c4s diff --git a/addons/twitcher/editor/inspector/twitch_icon.tscn b/addons/twitcher/editor/inspector/twitch_icon.tscn new file mode 100644 index 00000000..9651beac --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_icon.tscn @@ -0,0 +1,46 @@ +[gd_scene load_steps=2 format=3 uid="uid://dl3p3kb50dlxd"] + +[ext_resource type="Script" uid="uid://b7lnsgy0s2c4s" path="res://addons/twitcher/editor/inspector/twitch_icon.gd" id="1_kqhbg"] + +[node name="TwitchIcon" type="Button"] +custom_minimum_size = Vector2(112, 112) +anchors_preset = -1 +anchor_left = 0.451 +anchor_top = 0.414 +anchor_right = 0.549 +anchor_bottom = 0.633 +offset_left = 0.447937 +offset_top = -0.272034 +offset_right = -0.448059 +offset_bottom = -30.1841 +grow_horizontal = 2 +grow_vertical = 2 +tooltip_text = "Twitch API doesn't allow to update the icons via API you need to visit the Twitch UI for that" +script = ExtResource("1_kqhbg") +title = "Title" + +[node name="Image" type="TextureRect" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -0.5 +offset_top = -0.5 +offset_right = 0.5 +offset_bottom = 0.5 +grow_horizontal = 2 +grow_vertical = 2 +stretch_mode = 5 + +[node name="Title" type="Label" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 23.0 +grow_horizontal = 2 +text = "Title" +horizontal_alignment = 1 diff --git a/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd b/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd new file mode 100644 index 00000000..307dd33d --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd @@ -0,0 +1,46 @@ +@tool +extends EditorInspectorPlugin + +func _can_handle(object: Object) -> bool: + return object is TwitchMediaLoader + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: + if name == &"cache_emote": + var clear_emote_button = ClearCacheEditor.new(object.cache_emote) + add_property_editor("cache_cheermote", clear_emote_button, true, "Clear Emote Cache") + + if name == &"cache_badge": + var clear_badge_button = ClearCacheEditor.new(object.cache_badge) + add_property_editor("cache_cheermote", clear_badge_button, true, "Clear Badge Cache") + + if name == &"cache_cheermote": + var clear_cheermote_button = ClearCacheEditor.new(object.cache_cheermote) + add_property_editor("cache_cheermote", clear_cheermote_button, true, "Clear Cheermotes Cache") + + return false + + +class ClearCacheEditor extends EditorProperty: + + var _button: Button + + func _init(path: String) -> void: + _button = Button.new() + _button.text = "Clear" + _button.pressed.connect(_clear.bind(path)) + add_child(_button) + + + func _clear(path: String) -> void: + var dir: DirAccess = DirAccess.open(path) + for file: String in dir.get_files(): + if file.ends_with(".res"): + var err: Error = dir.remove(file) + if err != OK: + push_error("Can't delete %s/%s cause of %s" % [dir, file, error_string(err)]) + var tween = create_tween() + var _button_color = _button.modulate + tween.tween_property(_button, "modulate", Color.GREEN, .25) + tween.tween_property(_button, "modulate", _button_color, .25) + tween.play() diff --git a/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd.uid new file mode 100644 index 00000000..559bf4a0 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd.uid @@ -0,0 +1 @@ +uid://be76dqjl123t8 diff --git a/addons/twitcher/editor/inspector/twitch_property.gd b/addons/twitcher/editor/inspector/twitch_property.gd new file mode 100644 index 00000000..f5c0981a --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_property.gd @@ -0,0 +1,91 @@ +extends RefCounted + +## Helper class for easier editing of Project Settings +class_name TwitchProperty + +var key: String; +var default_value: Variant; + +func _init(k: String, default_val: Variant = "") -> void: + key = k; + default_value = default_val; + _add_property() + + +func _add_property(): + if not ProjectSettings.has_setting(key): + ProjectSettings.set_setting(key, default_value); + ProjectSettings.set_initial_value(key, default_value); + + +func get_val() -> Variant: + return ProjectSettings.get_setting_with_override(key); + + +func set_val(val) -> void: + ProjectSettings.set(key, val); + + +func basic() -> TwitchProperty: + ProjectSettings.set_as_basic(key, true); + return self; + + +func as_str(description: String = "") -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_PLACEHOLDER_TEXT, description); + + +func as_select(values: Array[String], optional: bool = true) -> TwitchProperty: + var hint_string = ",".join(values); + var enum_hint = PROPERTY_HINT_ENUM; + if optional: enum_hint = PROPERTY_HINT_ENUM_SUGGESTION; + return _add_type_def(TYPE_STRING, enum_hint, hint_string); + + +func as_bit_field(values: Array[String]) -> TwitchProperty: + var hint_string = ",".join(values); + return _add_type_def(TYPE_INT, PROPERTY_HINT_FLAGS, hint_string); + + +func as_password(description: String = "") -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_PASSWORD, description); + + +func as_bool(description: String = "") -> TwitchProperty: + return _add_type_def(TYPE_BOOL, PROPERTY_HINT_PLACEHOLDER_TEXT, description) + + +func as_num() -> TwitchProperty: + return _add_type_def(TYPE_INT, PROPERTY_HINT_NONE, "") + + +func as_global() -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE, ""); + + +func as_image() -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_FILE, "*.png,*.jpg,*.tres") + + +func as_dir() -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_DIR, ""); + + +## Type should be the generic type of the array +func as_list(type: Variant = "") -> TwitchProperty: + return _add_type_def(TYPE_ARRAY, PROPERTY_HINT_ARRAY_TYPE, type); + + +## The hint string can be a set of filters with wildcards like "*.png,*.jpg" +func as_global_save(file_types: String = "") -> TwitchProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_GLOBAL_SAVE_FILE, file_types) + + +func _add_type_def(type: int, hint: int, hint_string: Variant) -> TwitchProperty: + ProjectSettings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + "hint_string": hint_string + }) + return self diff --git a/addons/twitcher/editor/inspector/twitch_property.gd.uid b/addons/twitcher/editor/inspector/twitch_property.gd.uid new file mode 100644 index 00000000..e93dad62 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_property.gd.uid @@ -0,0 +1 @@ +uid://ci8bdo18wnodj diff --git a/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd b/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd new file mode 100644 index 00000000..026cd2d3 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd @@ -0,0 +1,91 @@ +@tool +extends TwitchRewardService + + +func load_reward(twitch_reward: TwitchReward) -> TwitchRewardService.LoadError: + var error: TwitchRewardService.LoadError = await super.load_reward(twitch_reward) + var toaster: EditorToaster = EditorInterface.get_editor_toaster() + match error: + TwitchRewardService.LoadError.NO_ID_AVAILABLE: + EditorInterface.get_editor_toaster().push_toast("Can't load %s it has no ID to load" % twitch_reward.title, EditorToaster.SEVERITY_ERROR) + TwitchRewardService.LoadError.NO_REWARD_FOUND: + pass + TwitchRewardService.LoadError.OK: + EditorInterface.get_editor_toaster().push_toast("Loaded '%s' from Twitch" % twitch_reward.title, EditorToaster.SEVERITY_INFO) + return error + + +func save_reward(twitch_reward: TwitchReward) -> TwitchRewardService.SaveError: + var error = await super.save_reward(twitch_reward) + match error: + TwitchRewardService.SaveError.OK: + EditorInterface.get_editor_toaster().push_toast("Saved the reward %s" % twitch_reward.title, EditorToaster.SEVERITY_INFO) + TwitchRewardService.SaveError.REWARD_NOT_OWNED: + EditorInterface.get_editor_toaster().push_toast("You can only update custom rewards from the application that created it in the first place.", EditorToaster.SEVERITY_ERROR) + TwitchRewardService.SaveError.UNKNOWN: + EditorInterface.get_editor_toaster().push_toast("Something unexpected happend during save", EditorToaster.SEVERITY_ERROR) + return error + + +func _convert_twitch_reward(twitch_reward: TwitchReward, reward: TwitchCustomReward) -> void: + convert_to_twitch_reward(twitch_reward, reward, media_loader) + + +## Helper method to update values from a TwitchCustomReward -> TwitchReward +static func convert_to_twitch_reward(twitch_reward: TwitchReward, reward: TwitchCustomReward, media_loader: TwitchMediaLoader = null) -> void: + var undo := EditorInterface.get_editor_undo_redo() + undo.create_action("Update Twitch Reward '%s'" % reward.title) + + undo.add_undo_property(twitch_reward, &"id", twitch_reward.id) + undo.add_undo_property(twitch_reward, &"title", twitch_reward.title) + undo.add_undo_property(twitch_reward, &"description", twitch_reward.description) + undo.add_undo_property(twitch_reward, &"cost", twitch_reward.cost) + undo.add_undo_property(twitch_reward, &"background_color", twitch_reward.background_color) + undo.add_undo_property(twitch_reward, &"is_enabled", twitch_reward.is_enabled) + undo.add_undo_property(twitch_reward, &"is_user_input_required", twitch_reward.is_user_input_required) + undo.add_undo_property(twitch_reward, &"is_max_per_stream_enabled", twitch_reward.is_max_per_stream_enabled) + undo.add_undo_property(twitch_reward, &"max_per_stream", twitch_reward.max_per_stream) + undo.add_undo_property(twitch_reward, &"is_max_per_user_per_stream_enabled", twitch_reward.is_max_per_user_per_stream_enabled) + undo.add_undo_property(twitch_reward, &"max_per_user_per_stream", twitch_reward.max_per_user_per_stream) + undo.add_undo_property(twitch_reward, &"is_global_cooldown_enabled", twitch_reward.is_global_cooldown_enabled) + undo.add_undo_property(twitch_reward, &"global_cooldown_seconds", twitch_reward.global_cooldown_seconds) + undo.add_undo_property(twitch_reward, &"is_paused", twitch_reward.is_paused) + undo.add_undo_property(twitch_reward, &"should_redemptions_skip_request_queue", twitch_reward.should_redemptions_skip_request_queue) + undo.add_undo_property(twitch_reward, &"is_in_stock", twitch_reward.is_in_stock) + undo.add_undo_property(twitch_reward, &"redemptions_redeemed_current_stream", twitch_reward.redemptions_redeemed_current_stream) + undo.add_undo_property(twitch_reward, &"cooldown_expires_at", twitch_reward.cooldown_expires_at) + undo.add_undo_method(twitch_reward, &"emit_changed") + + undo.add_do_property(twitch_reward, &"id", reward.id) + undo.add_do_property(twitch_reward, &"title", reward.title) + undo.add_do_property(twitch_reward, &"description", reward.prompt) + undo.add_do_property(twitch_reward, &"cost", reward.cost) + undo.add_do_property(twitch_reward, &"background_color", Color.html(reward.background_color)) + undo.add_do_property(twitch_reward, &"is_enabled", reward.is_enabled) + undo.add_do_property(twitch_reward, &"is_user_input_required", reward.is_user_input_required) + undo.add_do_property(twitch_reward, &"is_max_per_stream_enabled", reward.max_per_stream_setting.is_enabled) + undo.add_do_property(twitch_reward, &"max_per_stream", reward.max_per_stream_setting.max_per_stream) + undo.add_do_property(twitch_reward, &"is_max_per_user_per_stream_enabled", reward.max_per_user_per_stream_setting.is_enabled) + undo.add_do_property(twitch_reward, &"max_per_user_per_stream", reward.max_per_user_per_stream_setting.max_per_user_per_stream) + undo.add_do_property(twitch_reward, &"is_global_cooldown_enabled", reward.global_cooldown_setting.is_enabled) + undo.add_do_property(twitch_reward, &"global_cooldown_seconds", reward.global_cooldown_setting.global_cooldown_seconds) + undo.add_do_property(twitch_reward, &"is_paused", reward.is_paused) + undo.add_do_property(twitch_reward, &"should_redemptions_skip_request_queue", reward.should_redemptions_skip_request_queue) + undo.add_do_property(twitch_reward, &"is_in_stock", twitch_reward.is_in_stock) + undo.add_do_property(twitch_reward, &"redemptions_redeemed_current_stream", twitch_reward.redemptions_redeemed_current_stream) + undo.add_do_property(twitch_reward, &"cooldown_expires_at", twitch_reward.cooldown_expires_at) + undo.add_do_method(twitch_reward, &"emit_changed") + + undo.commit_action() + + # Doesn't undo this ones it doesn't make sense the value is temporary and the actual info from + # Twitch. With undoing them you wouldn't reset Twitch :D + + if reward.image && media_loader: + twitch_reward.image_1 = await media_loader.load_image(reward.image.url_1x) + twitch_reward.image_2 = await media_loader.load_image(reward.image.url_2x) + twitch_reward.image_4 = await media_loader.load_image(reward.image.url_3x) + + twitch_reward.is_in_stock = reward.is_in_stock + twitch_reward.redemptions_redeemed_current_stream = reward.redemptions_redeemed_current_stream + twitch_reward.cooldown_expires_at = reward.cooldown_expires_at diff --git a/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd.uid b/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd.uid new file mode 100644 index 00000000..ed4efd31 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_editor_service.gd.uid @@ -0,0 +1 @@ +uid://dg6o33ymscos2 diff --git a/addons/twitcher/editor/inspector/twitch_reward_info.gd b/addons/twitcher/editor/inspector/twitch_reward_info.gd new file mode 100644 index 00000000..18288ce9 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_info.gd @@ -0,0 +1,193 @@ +@tool +extends Control + +const TwitchIcon = preload("res://addons/twitcher/editor/inspector/twitch_icon.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchEditorNodeUtils = preload("res://addons/twitcher/editor/twitch_editor_node_utils.gd") +const TwitchRewardEditorService = preload("res://addons/twitcher/editor/inspector/twitch_reward_editor_service.gd") + +# For checking if the cooldown time is in minutes or days +const MINUTE_IN_SEC: int = 60 +const HOUR_IN_SEC: int = 60 * MINUTE_IN_SEC +const DAY_IN_SEC: int = 24 * HOUR_IN_SEC + +# Consts for the CooldownTimeUnit indices +const CD_UNIT_SECONDS: int = 0 +const CD_UNIT_MINUTE: int = 1 +const CD_UNIT_HOUR: int = 2 +const CD_UNIT_DAY: int = 3 + +@export var token: OAuthToken +@export var setting: OAuthSetting + +@onready var id: Label = %ID +@onready var title: LineEdit = %Title +@onready var description: TextEdit = %Description +@onready var require_viewer_text: CheckButton = %RequireViewerText +@onready var cost: SpinBox = %Cost +@onready var icon_28: TwitchIcon = %Icon28 +@onready var icon_56: TwitchIcon = %Icon56 +@onready var icon_112: TwitchIcon = %Icon112 +@onready var background_color: ColorPickerButton = %BackgroundColor +@onready var skip_reward_queue: CheckButton = %SkipRewardQueue +@onready var cooldown_time: SpinBox = %CooldownTime +@onready var cooldown_time_unit: OptionButton = %CooldownTimeUnit +@onready var limit_per_stream: SpinBox = %LimitPerStream +@onready var limit_per_stream_per_user: SpinBox = %LimitPerStreamPerUser +@onready var enable: CheckButton = %Enable +@onready var pause: CheckButton = %Pause + +@onready var in_stock: CheckButton = %InStock +@onready var current_stream: SpinBox = %CurrentStream +@onready var cooldown_expire_at: Label = %CooldownExpireAt + +@onready var load: Button = %Load +@onready var save: Button = %Save +@onready var delete: Button = %Delete + + +var undo_redo: EditorUndoRedoManager +var twitch_reward: TwitchReward: + set = _update_twitch_reward + +var _cooldown_multiplier: int: + get(): + match cooldown_time_unit.selected: + CD_UNIT_MINUTE: return MINUTE_IN_SEC + CD_UNIT_HOUR: return HOUR_IN_SEC + CD_UNIT_DAY: return DAY_IN_SEC + _: return 1 + + +func _ready() -> void: + load.pressed.connect(_on_load) + save.pressed.connect(_on_save) + delete.pressed.connect(_on_delete) + + if twitch_reward == null: return # Happens on bootup + _update_twitch_reward(twitch_reward) + + # Bind Data Changes to Resource + enable.toggled.connect(func (state: bool) -> void: twitch_reward.is_enabled = state) + pause.toggled.connect(func (state: bool) -> void: twitch_reward.is_paused = state) + title.text_changed.connect(func(text: String) -> void: twitch_reward.title = text) + description.text_changed.connect(func() -> void: twitch_reward.description = description.text) + require_viewer_text.toggled.connect(func(state: bool) -> void: twitch_reward.is_user_input_required = state) + cost.value_changed.connect(func(new_value: float) -> void: twitch_reward.cost = new_value) + background_color.color_changed.connect(func(new_color: Color) -> void: twitch_reward.background_color = new_color.to_html(false)) + skip_reward_queue.toggled.connect(func(state: bool) -> void: twitch_reward.should_redemptions_skip_request_queue = state) + cooldown_time.value_changed.connect(func(new_value: float) -> void: + twitch_reward.global_cooldown_seconds = new_value * _cooldown_multiplier + twitch_reward.is_global_cooldown_enabled = twitch_reward.global_cooldown_seconds > 0 + ) + limit_per_stream.value_changed.connect(func(new_value: float) -> void: + twitch_reward.max_per_stream = new_value + twitch_reward.is_max_per_stream_enabled = new_value > 0 + ) + limit_per_stream_per_user.value_changed.connect(func(new_value: float) -> void: + twitch_reward.max_per_user_per_stream = new_value + twitch_reward.is_max_per_user_per_stream_enabled = new_value > 0 + ) + + +func _update_twitch_reward(reward: TwitchReward) -> void: + if twitch_reward != reward: + if twitch_reward: + var old_update_method: Callable = _update_twitch_reward.bind(twitch_reward) + if twitch_reward.changed.is_connected(old_update_method): + twitch_reward.changed.disconnect(old_update_method) + + twitch_reward = reward + var update_method: Callable = _update_twitch_reward.bind(reward) + if not reward.changed.is_connected(update_method): + reward.changed.connect(update_method) + + if not is_node_ready(): return + if reward == null: + load.disabled = true + save.disabled = true + return + + id.text = reward.id if reward.id else "NEW" + title.text = reward.title + description.text = reward.description + require_viewer_text.set_pressed_no_signal(reward.is_user_input_required) + cost.value = reward.cost + icon_28.texture = reward.get_image1() + icon_56.texture = reward.get_image2() + icon_112.texture = reward.get_image4() + background_color.color = reward.background_color + skip_reward_queue.set_pressed_no_signal(reward.should_redemptions_skip_request_queue) + _update_cooldown(reward.global_cooldown_seconds) + limit_per_stream.value = reward.max_per_stream + limit_per_stream_per_user.value = reward.max_per_user_per_stream + + enable.set_pressed_no_signal(reward.is_enabled) + pause.set_pressed_no_signal(reward.is_paused) + + in_stock.set_pressed_no_signal(reward.is_in_stock) + current_stream.set_value_no_signal(reward.redemptions_redeemed_current_stream) + cooldown_expire_at.text = reward.cooldown_expires_at if reward.cooldown_expires_at else "no data" + + delete.disabled = reward.id == "" + load.disabled = reward.id == "" + save.disabled = false + + + +func _update_cooldown(cd_in_sec: int) -> void: + if(fmod(cd_in_sec, DAY_IN_SEC) == 0): + cooldown_time.value = cd_in_sec / DAY_IN_SEC + cooldown_time_unit.select(CD_UNIT_DAY) + elif(fmod(cd_in_sec, HOUR_IN_SEC) == 0): + cooldown_time.value = cd_in_sec / HOUR_IN_SEC + cooldown_time_unit.select(CD_UNIT_HOUR) + elif(fmod(cd_in_sec, MINUTE_IN_SEC) == 0): + cooldown_time.value = cd_in_sec / MINUTE_IN_SEC + cooldown_time_unit.select(CD_UNIT_MINUTE) + else: + cooldown_time.value = cd_in_sec + cooldown_time_unit.select(CD_UNIT_SECONDS) + +#region Twitch Interactions + +func _on_load() -> void: + var api: TwitchAPI = TwitchEditorNodeUtils.create_api(token, setting) + add_child(api) + var media_loader: TwitchMediaLoader = TwitchEditorNodeUtils.create_media_loader(api) + add_child(media_loader) + var twitch_reward_service: TwitchRewardEditorService = TwitchRewardEditorService.new(api, media_loader) + await twitch_reward_service.load_reward(twitch_reward) + api.queue_free() + media_loader.queue_free() + + +func _on_save() -> void: + var api: TwitchAPI = TwitchEditorNodeUtils.create_api(token, setting) + add_child(api) + var media_loader: TwitchMediaLoader = TwitchEditorNodeUtils.create_media_loader(api) + add_child(media_loader) + var twitch_reward_service: TwitchRewardEditorService = TwitchRewardEditorService.new(api, media_loader) + await twitch_reward_service.save_reward(twitch_reward) + api.queue_free() + media_loader.queue_free() + + +func _on_delete() -> void: + var dialog = ConfirmationDialog.new() + dialog.dialog_text = "Are you sure you want to delete the reward '%s' on Twitch?" % twitch_reward.title + dialog.ok_button_text = "Yes" + dialog.cancel_button_text = "No" + dialog.title = "Delete Reward" + dialog.get_ok_button().pressed.connect(func(): + var api: TwitchAPI = TwitchEditorNodeUtils.create_api(token, setting) + add_child(api) + var twitch_reward_service: TwitchRewardEditorService = TwitchRewardEditorService.new(api, null) + await twitch_reward_service.delete_reward(twitch_reward) + api.queue_free() + EditorInterface.get_editor_toaster().push_toast("Deleted reward '%s' succesfully" % twitch_reward.title) + ) + EditorInterface.popup_dialog_centered(dialog) + dialog.get_cancel_button().grab_focus() + +#endregion diff --git a/addons/twitcher/editor/inspector/twitch_reward_info.gd.uid b/addons/twitcher/editor/inspector/twitch_reward_info.gd.uid new file mode 100644 index 00000000..fdda187f --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_info.gd.uid @@ -0,0 +1 @@ +uid://c1ywjkps543oc diff --git a/addons/twitcher/editor/inspector/twitch_reward_info.tscn b/addons/twitcher/editor/inspector/twitch_reward_info.tscn new file mode 100644 index 00000000..9eb32e08 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_info.tscn @@ -0,0 +1,356 @@ +[gd_scene load_steps=7 format=3 uid="uid://b8wtfcis561kc"] + +[ext_resource type="Script" uid="uid://c1ywjkps543oc" path="res://addons/twitcher/editor/inspector/twitch_reward_info.gd" id="1_kni7l"] +[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/twitcher_title_label_settings.tres" id="2_rdy0d"] +[ext_resource type="PackedScene" uid="uid://dl3p3kb50dlxd" path="res://addons/twitcher/editor/inspector/twitch_icon.tscn" id="2_toepa"] +[ext_resource type="LabelSettings" uid="uid://3kr4x2rxu4kd" path="res://addons/twitcher/assets/twitcher_description_label_settings.tres" id="3_rdy0d"] + +[sub_resource type="LabelSettings" id="LabelSettings_toepa"] +font_color = Color(1, 1, 1, 0.227451) + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_b8j3v"] + +[node name="RewardInpsector" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_kni7l") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="TitleLabel" type="Label" parent="VBoxContainer/HBoxContainer3"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Name" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="ID" type="Label" parent="VBoxContainer/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +label_settings = SubResource("LabelSettings_toepa") +horizontal_alignment = 2 + +[node name="Title" type="LineEdit" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="DescriptionLabel" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Description" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="Description" type="TextEdit" parent="VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 80) +layout_mode = 2 + +[node name="ViewerTextLabel" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Require Viewer to Enter Text" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="RequireViewerText" type="CheckButton" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "If enabled, a required text field will appear to viewers in the reward." +autowrap_mode = 3 + +[node name="CostTitle" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Cost" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="Cost" type="SpinBox" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +min_value = 1.0 +max_value = 100000.0 +value = 1.0 +rounded = true +allow_greater = true +custom_arrow_step = 110.0 + +[node name="CostInfo" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Tip: Viewers earn 220 points per hour on average. Subs earn multipliers up to 2x." +label_settings = ExtResource("3_rdy0d") +autowrap_mode = 3 + +[node name="IconTitle" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Reward Icon" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="IconContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="Icon28" parent="VBoxContainer/IconContainer" instance=ExtResource("2_toepa")] +unique_name_in_owner = true +layout_mode = 2 +title = "28x28" + +[node name="Icon56" parent="VBoxContainer/IconContainer" instance=ExtResource("2_toepa")] +unique_name_in_owner = true +layout_mode = 2 +title = "56x56" + +[node name="Icon112" parent="VBoxContainer/IconContainer" instance=ExtResource("2_toepa")] +unique_name_in_owner = true +layout_mode = 2 +title = "112x112" + +[node name="IconInfo" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +text = "Icons should be PNGs with transparent backgrounds and a maximum file size of 25kb. Can only be updated via Twitch UI." +label_settings = ExtResource("3_rdy0d") +autowrap_mode = 3 + +[node name="BackgroundColorTitle" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Background Color" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="BackgroundColor" type="ColorPickerButton" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Background" +edit_alpha = false + +[node name="SkipRewardQueueTitle" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Skip Reward Requests Queue" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="SkipRewardQueue" type="CheckButton" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "If enabled, only future viewer requests will skip the queue for review." +autowrap_mode = 3 + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +layout_mode = 2 + +[node name="CooldownTitle" type="Label" parent="VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Cooldown & Limits" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="CooldownTimeTitle" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Redemption Cooldown Time" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="CooldownTime" type="SpinBox" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +max_value = 60.0 +value = 1.0 +rounded = true +allow_greater = true + +[node name="CooldownTimeUnit" type="OptionButton" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +selected = 0 +item_count = 4 +popup/item_0/text = "Seconds" +popup/item_0/id = 0 +popup/item_1/text = "Minutes" +popup/item_1/id = 1 +popup/item_2/text = "Hours" +popup/item_2/id = 2 +popup/item_3/text = "Days" +popup/item_3/id = 3 + +[node name="CooldownTimeInfo" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(40, 0) +layout_mode = 2 +text = "Time between redemptions, up to 7 days" +label_settings = ExtResource("3_rdy0d") +autowrap_mode = 3 + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 +theme_override_styles/separator = SubResource("StyleBoxEmpty_b8j3v") + +[node name="LimitPerStreamTitle" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Limit Redemptions Per Stream" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="LimitPerStream" type="SpinBox" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true + +[node name="LimitPerStreamInfo" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Max total redemptions for viewers" +label_settings = ExtResource("3_rdy0d") +autowrap_mode = 3 + +[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 +theme_override_styles/separator = SubResource("StyleBoxEmpty_b8j3v") + +[node name="LimitPerStreamPerUserTitle" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Limit Redemptions Per User Per Stream" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="LimitPerStreamPerUser" type="SpinBox" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +rounded = true +allow_greater = true + +[node name="LimitPerStreamPerUserInfo" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Max individual redemptions per viewer per stream" +label_settings = ExtResource("3_rdy0d") +autowrap_mode = 3 + +[node name="HSeparator3" type="HSeparator" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 +theme_override_styles/separator = SubResource("StyleBoxEmpty_b8j3v") + +[node name="HBoxContainer2" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer2"] +layout_mode = 2 + +[node name="Enable" type="CheckButton" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Reward Enabled" + +[node name="Pause" type="CheckButton" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Reward Paused" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +layout_mode = 2 + +[node name="TempTitle" type="Label" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "Temporary Infos from the current Stream:" +label_settings = ExtResource("2_rdy0d") +autowrap_mode = 3 + +[node name="InStock" type="CheckButton" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = "Is in stock" + +[node name="OtherTempContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer2/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="CurrentStreamTitle" type="Label" parent="VBoxContainer/HBoxContainer2/VBoxContainer/OtherTempContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Redeemed during current stream:" + +[node name="CurrentStream" type="SpinBox" parent="VBoxContainer/HBoxContainer2/VBoxContainer/OtherTempContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 8 +editable = false + +[node name="CooldownExpireAtTitle" type="Label" parent="VBoxContainer/HBoxContainer2/VBoxContainer/OtherTempContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Cooldown Expired at:" + +[node name="CooldownExpireAt" type="Label" parent="VBoxContainer/HBoxContainer2/VBoxContainer/OtherTempContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "-" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Delete" type="Button" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +self_modulate = Color(1, 0.4, 0.4, 1) +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true +text = "Delete from Twitch" + +[node name="Load" type="Button" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +self_modulate = Color(0.926993, 0.496872, 1.92523e-06, 1) +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true +text = "Import from Twitch" + +[node name="Save" type="Button" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +self_modulate = Color(0.69, 1, 0.4, 1) +layout_mode = 2 +size_flags_horizontal = 3 +disabled = true +text = "Save to Twitch" diff --git a/addons/twitcher/editor/inspector/twitch_reward_inspector.gd b/addons/twitcher/editor/inspector/twitch_reward_inspector.gd new file mode 100644 index 00000000..5a6f8e26 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_inspector.gd @@ -0,0 +1,39 @@ +@tool +extends EditorInspectorPlugin + +const TWITCH_REWARD_INFO: PackedScene = preload("res://addons/twitcher/editor/inspector/twitch_reward_info.tscn") +const TwitchAuthorizeEditor = preload("res://addons/twitcher/editor/inspector/twitch_authorize_editor.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + + +func _can_handle(object: Object) -> bool: + return object is TwitchReward + + +func _parse_category(object: Object, category: String) -> void: + if category == "twitch_reward.gd": + if TwitchEditorSettings.is_valid(): + var info: Node = TWITCH_REWARD_INFO.instantiate() + info.twitch_reward = object + add_custom_control(info) + #else: + # Actually I wanted to show the TwitchAuthorizeEditor in case the editor is not authorized yet. + # But in reality the TwitchUser Inspector is doing that for the broadcaster user of the + # TwitchReward already and steals the job from this one :D + + #var authorize_editor = TwitchAuthorizeEditor.new(name) + #authorize_editor.authorized.connect(_on_authorized.bind(object), CONNECT_DEFERRED) + #add_custom_control(authorize_editor) + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, \ + hint_string: String, usage_flags: int, wide: bool) -> bool: + # remove the current editor for all properties + + # If editor is authorized (true) remove all properties / otherwise show default insepctor (false) + return TwitchEditorSettings.is_valid() + + +func _on_authorized(object: Object) -> void: + EditorInterface.get_inspector().edit(null) + EditorInterface.get_inspector().edit(object) diff --git a/addons/twitcher/editor/inspector/twitch_reward_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_reward_inspector.gd.uid new file mode 100644 index 00000000..5b7d8c43 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_reward_inspector.gd.uid @@ -0,0 +1 @@ +uid://rbl2hh7p3uxy diff --git a/addons/twitcher/editor/inspector/twitch_scope_inspector.gd b/addons/twitcher/editor/inspector/twitch_scope_inspector.gd new file mode 100644 index 00000000..767f3a20 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_scope_inspector.gd @@ -0,0 +1,13 @@ +extends EditorInspectorPlugin + +const TwitchScopeProperty = preload("res://addons/twitcher/editor/inspector/twitch_scope_property.gd") + +func _can_handle(object: Object) -> bool: + return object is TwitchOAuthScopes + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: + if name == "used_scopes": + add_property_editor("used_scopes", TwitchScopeProperty.new(), true); + return false; + return false diff --git a/addons/twitcher/editor/inspector/twitch_scope_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_scope_inspector.gd.uid new file mode 100644 index 00000000..7c1b8556 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_scope_inspector.gd.uid @@ -0,0 +1 @@ +uid://c7832kf7htajv diff --git a/addons/twitcher/editor/inspector/twitch_scope_property.gd b/addons/twitcher/editor/inspector/twitch_scope_property.gd new file mode 100644 index 00000000..ecd038e6 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_scope_property.gd @@ -0,0 +1,51 @@ +extends EditorProperty + +const TITLE_SETTING: LabelSettings = preload("uid://bnsxy6gcm8q11") + +var _scope_checkboxes: Dictionary[StringName, CheckBox] +var grid: GridContainer = GridContainer.new(); + +signal scope_selected(scope: TwitchScope.Definition) + +func _init() -> void: + grid.columns = 1; + var grouped_scopes = TwitchScope.get_grouped_scopes(); + for category: String in grouped_scopes: + var title_category = Label.new(); + title_category.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; + title_category.text = category.capitalize(); + title_category.label_settings = TITLE_SETTING; + grid.add_child(title_category); + grid.add_child(Control.new()); + + for scope: TwitchScope.Definition in grouped_scopes[category]: + var checkbox: CheckBox = CheckBox.new(); + checkbox.text = scope.value; + checkbox.toggled.connect(_on_checkbox_pressed.bind(scope)) + checkbox.tooltip_text = scope.description + _scope_checkboxes[scope.value] = checkbox + grid.add_child(checkbox); + add_focusable(checkbox); + add_child(grid); + + +func _on_scope_changed() -> void: + update_property() + + +func _update_property() -> void: + for scope: StringName in _scope_checkboxes.keys(): + var checkbox: CheckBox = _scope_checkboxes[scope]; + var scopes: OAuthScopes = get_edited_object() + checkbox.button_pressed = scopes.used_scopes.find(scope) != -1; + + +func _on_checkbox_pressed(toggled_on: bool, scope: TwitchScope.Definition) -> void: + var scopes: OAuthScopes = get_edited_object() + if toggled_on: + if scopes.used_scopes.find(scope.value) == -1: + scopes.used_scopes.append(scope.value) + else: + scopes.used_scopes.erase(scope.value) + emit_changed("used_scopes", scopes.used_scopes, &"", true) + scope_selected.emit(scope) diff --git a/addons/twitcher/editor/inspector/twitch_scope_property.gd.uid b/addons/twitcher/editor/inspector/twitch_scope_property.gd.uid new file mode 100644 index 00000000..bf04d153 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_scope_property.gd.uid @@ -0,0 +1 @@ +uid://xldq5o0osdgq diff --git a/addons/twitcher/editor/inspector/twitch_token_info.gd b/addons/twitcher/editor/inspector/twitch_token_info.gd new file mode 100644 index 00000000..74c94fa6 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_info.gd @@ -0,0 +1,14 @@ +@tool +extends "res://addons/twitcher/lib/oOuch/oauth_token_info.gd" + +const TWITCH_TOKEN_REVOKE_POPUP = preload("res://addons/twitcher/editor/inspector/twitch_token_revoke_popup.tscn") +const TwitchTokenRevokePopup = preload("res://addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd") + + +func _on_revoke_pressed() -> void: + var popup: TwitchTokenRevokePopup = TWITCH_TOKEN_REVOKE_POPUP.instantiate() + popup.token = token + add_child(popup) + popup.popup_centered() + var success = await popup.revoked + if success: _reset_token() diff --git a/addons/twitcher/editor/inspector/twitch_token_info.gd.uid b/addons/twitcher/editor/inspector/twitch_token_info.gd.uid new file mode 100644 index 00000000..dbd63066 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_info.gd.uid @@ -0,0 +1 @@ +uid://c8no6da8ae0xt diff --git a/addons/twitcher/editor/inspector/twitch_token_info.tscn b/addons/twitcher/editor/inspector/twitch_token_info.tscn new file mode 100644 index 00000000..6788985a --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_info.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=3 uid="uid://c7qvkjw425jaf"] + +[ext_resource type="PackedScene" uid="uid://6d2jst8ga4le" path="res://addons/twitcher/lib/oOuch/oauth_token_info.tscn" id="1_0mxfe"] +[ext_resource type="Script" uid="uid://c8no6da8ae0xt" path="res://addons/twitcher/editor/inspector/twitch_token_info.gd" id="2_kejyg"] + +[node name="TokenInfo" instance=ExtResource("1_0mxfe")] +script = ExtResource("2_kejyg") diff --git a/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd new file mode 100644 index 00000000..3267519c --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd @@ -0,0 +1,50 @@ +@tool +extends Window + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + +@export var token: OAuthToken + +@onready var inspector: HBoxContainer = %Inspector +@onready var cancel: Button = %Cancel +@onready var revoke_locally: Button = %RevokeLocally +@onready var revoke_twitch: Button = %RevokeTwitch +@onready var twitch_token_handler: TwitchTokenHandler = %TwitchTokenHandler + +signal revoked(success: bool) + +var picker: EditorResourcePicker + +func _ready() -> void: + picker = EditorResourcePicker.new() + picker.base_type = "OAuthSetting" + picker.edited_resource = TwitchEditorSettings.game_oauth_setting + picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + inspector.add_child(picker) + + cancel.pressed.connect(_on_cancel) + revoke_locally.pressed.connect(_on_revoke_locally) + revoke_twitch.pressed.connect(_on_revoke_twitch) + + twitch_token_handler.token = token + close_requested.connect(_on_cancel) + + +func _on_cancel() -> void: + revoked.emit(false) + queue_free() + + +func _on_revoke_locally() -> void: + revoked.emit(true) + token.remove_tokens() + queue_free() + + +func _on_revoke_twitch() -> void: + revoked.emit(true) + if is_instance_valid(picker.edited_resource): + twitch_token_handler.oauth_setting = picker.edited_resource + await twitch_token_handler.revoke_token() + queue_free() diff --git a/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd.uid b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd.uid new file mode 100644 index 00000000..3090b834 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd.uid @@ -0,0 +1 @@ +uid://bp1fga8addrlc diff --git a/addons/twitcher/editor/inspector/twitch_token_revoke_popup.tscn b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.tscn new file mode 100644 index 00000000..93c09370 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_token_revoke_popup.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=6 format=3 uid="uid://b4n67bt8ni6ge"] + +[ext_resource type="Script" uid="uid://bp1fga8addrlc" path="res://addons/twitcher/editor/inspector/twitch_token_revoke_popup.gd" id="1_4n8su"] +[ext_resource type="Script" uid="uid://blnbogtrshw4r" path="res://addons/twitcher/auth/twitch_token_handler.gd" id="2_iycl8"] +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="3_4n8su"] +[ext_resource type="Script" uid="uid://b52xp7c23ucfk" path="res://addons/twitcher/lib/oOuch/oauth_token.gd" id="4_iycl8"] + +[sub_resource type="Resource" id="Resource_4n8su"] +script = ExtResource("4_iycl8") +_crypto_key_provider = ExtResource("3_4n8su") +_identifier = "Auth-357" +_cache_path = "user://auth.conf" + +[node name="TokenRevokePopup" type="Window"] +title = "Revoke Token" +position = Vector2i(0, 36) +size = Vector2i(400, 200) +script = ExtResource("1_4n8su") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 50 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "You can decide to revoke the token locally or actually invalidate it on Twitch side too. To revoke it on Twitch the client id must be known: " +autowrap_mode = 3 + +[node name="Inspector" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Inspector"] +layout_mode = 2 +text = "OAuth Setting:" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 10 + +[node name="Cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Cancel" + +[node name="RevokeTwitch" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Revoke on Twitch" + +[node name="RevokeLocally" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Revoke Locally" + +[node name="TwitchTokenHandler" type="Node" parent="."] +unique_name_in_owner = true +script = ExtResource("2_iycl8") +token = SubResource("Resource_4n8su") +metadata/_custom_type_script = "uid://blnbogtrshw4r" diff --git a/addons/twitcher/editor/inspector/twitch_user_inspector.gd b/addons/twitcher/editor/inspector/twitch_user_inspector.gd new file mode 100644 index 00000000..786d5962 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_user_inspector.gd @@ -0,0 +1,28 @@ +@tool +extends EditorInspectorPlugin + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const UserProperty = preload("res://addons/twitcher/editor/inspector/user_property.gd") +const TwitchAuthorizeEditor = preload("res://addons/twitcher/editor/inspector/twitch_authorize_editor.gd") + +func _can_handle(object: Object) -> bool: + return true + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: + if hint_string == "TwitchUser": + + if TwitchEditorSettings.is_valid(): + add_property_editor(name, UserProperty.new()) + return true + else: + var authorize_editor = TwitchAuthorizeEditor.new(name) + authorize_editor.authorized.connect(_on_authorized.bind(object), CONNECT_DEFERRED) + add_custom_control(authorize_editor) + + return false + + +func _on_authorized(object: Object) -> void: + EditorInterface.get_inspector().edit(null) + EditorInterface.get_inspector().edit(object) diff --git a/addons/twitcher/editor/inspector/twitch_user_inspector.gd.uid b/addons/twitcher/editor/inspector/twitch_user_inspector.gd.uid new file mode 100644 index 00000000..67aaa9e4 --- /dev/null +++ b/addons/twitcher/editor/inspector/twitch_user_inspector.gd.uid @@ -0,0 +1 @@ +uid://cpq13q33gcqov diff --git a/addons/twitcher/editor/inspector/user_converter.gd b/addons/twitcher/editor/inspector/user_converter.gd new file mode 100644 index 00000000..6cc9418d --- /dev/null +++ b/addons/twitcher/editor/inspector/user_converter.gd @@ -0,0 +1,155 @@ +@tool +extends HBoxContainer + +class_name UserConverter + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd") + +@onready var _login: LineEdit = %Login +@onready var _id: LineEdit = %Id +@onready var _swap_view: Button = %SwapView +@onready var search: Button = %Search + +@export var user: TwitchUser +@export var token: OAuthToken +@export var setting: OAuthSetting + +static var _current_user: TwitchUser + +var user_login: String: + set = _update_user_login + +var user_id: String: + set = _update_user_id + + +signal changed(user: TwitchUser) + + +func _ready() -> void: + _login.text_changed.connect(_on_login_changed) + _login.text_submitted.connect(_on_text_submitted) + _login.focus_exited.connect(_on_focus_exited) + _id.text_changed.connect(_on_id_changed) + _id.text_submitted.connect(_on_text_submitted) + _id.focus_exited.connect(_on_focus_exited) + _swap_view.pressed.connect(_on_swap_view) + _load_current_user.call_deferred() + search.pressed.connect(_on_changed) + + +func update_user(new_user: TwitchUser, notify: bool = false) -> void: + if new_user: + user_login = new_user.login + user_id = new_user.id + else: + user_login = "" + user_id = "" + user = new_user + if notify: changed.emit(new_user) + + +## Experimental tries to load user from api key +func _load_current_user() -> void: + if _current_user == null: + var users: TwitchGetUsers.Opt = TwitchGetUsers.Opt.new() + _current_user = await _load_user(users) + + if _current_user && not user_login && not user_id && TwitchEditorSettings.load_current_twitch_user: + update_user(_current_user, true) + + +func _on_swap_view() -> void: + if _login.visible: + _login.visible = false + _id.visible = true + _swap_view.text = "ID" + else: + _login.visible = true + _id.visible = false + _swap_view.text = "Name" + + +func _update_user_login(val: String) -> void: + user_login = val + _login.text = val + _login.caret_column = val.length() + + +func _update_user_id(val: String) -> void: + user_id = val + _id.text = val + _id.caret_column = val.length() + + +func _on_id_changed(new_text: String) -> void: + _login.text = "" + TwitchTweens.loading(self, Color.AQUA) + + +func _on_login_changed(new_text: String) -> void: + _id.text = "" + TwitchTweens.loading(self, Color.AQUA) + + +func reload() -> void: + var new_user_login: String = _login.text + var new_user_id: String = _id.text + if new_user_id == "" && new_user_login == "": + await TwitchTweens.flash(self, Color.GREEN) + changed.emit(null) + return + + if user != null && new_user_login == user.login && new_user_id == user.id: + return + + var users: TwitchGetUsers.Opt = TwitchGetUsers.Opt.new() + + if new_user_login != "" && (not user || user.login != new_user_login): + users.login = [ new_user_login ] + elif new_user_id != "" && (not user || user.id != new_user_id): + users.id = [ new_user_id ] + + var new_user: TwitchUser = await _load_user(users) + if new_user != null: update_user(new_user, true) + + +func _load_user(users: TwitchGetUsers.Opt) -> TwitchUser: + TwitchTweens.loading(self) + if users.id != null || users.login != null: + var new_user = await _get_user(users) + if not new_user: + await TwitchTweens.flash(self, Color.RED) + else: + await TwitchTweens.flash(self, Color.GREEN) + return new_user + return null + + +func _on_focus_exited() -> void: + reload() + + +func _on_text_submitted(new_text: String) -> void: + reload() + + +func _on_changed() -> void: + reload() + + +func _get_user(get_user_opt: TwitchGetUsers.Opt) -> TwitchUser: + var api: TwitchAPI = TwitchAPI.new() + var _token: OAuthToken = token if token else TwitchEditorSettings.editor_oauth_token + var _setting: OAuthSetting = setting if setting else TwitchEditorSettings.editor_oauth_setting + api.token = _token + api.oauth_setting = _setting + add_child(api) + var response: TwitchGetUsers.Response = await api.get_users(get_user_opt) + var data: Array[TwitchUser] = response.data + if data.is_empty(): + printerr("User %s%s was not found." % [ get_user_opt.login, get_user_opt.id ]) + return null + remove_child(api) + return data[0] diff --git a/addons/twitcher/editor/inspector/user_converter.gd.uid b/addons/twitcher/editor/inspector/user_converter.gd.uid new file mode 100644 index 00000000..59d03c76 --- /dev/null +++ b/addons/twitcher/editor/inspector/user_converter.gd.uid @@ -0,0 +1 @@ +uid://b6qdiwr7rawx1 diff --git a/addons/twitcher/editor/inspector/user_converter.tscn b/addons/twitcher/editor/inspector/user_converter.tscn new file mode 100644 index 00000000..35bb5041 --- /dev/null +++ b/addons/twitcher/editor/inspector/user_converter.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=3 format=3 uid="uid://cus81w3pidhjo"] + +[ext_resource type="Script" uid="uid://b6qdiwr7rawx1" path="res://addons/twitcher/editor/inspector/user_converter.gd" id="1_ior8m"] +[ext_resource type="Texture2D" uid="uid://1e6nrtqsuc6" path="res://addons/twitcher/assets/icon_search.tres" id="2_t7vdb"] + +[node name="UserConverter" type="HBoxContainer"] +modulate = Color(1, 1, 0, 1) +offset_right = 40.0 +offset_bottom = 40.0 +size_flags_horizontal = 3 +script = ExtResource("1_ior8m") + +[node name="Login" type="LineEdit" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "User Login" + +[node name="Id" type="LineEdit" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "User ID" + +[node name="SwapView" type="Button" parent="."] +unique_name_in_owner = true +layout_mode = 2 +text = "Name" + +[node name="Search" type="Button" parent="."] +unique_name_in_owner = true +layout_mode = 2 +icon = ExtResource("2_t7vdb") diff --git a/addons/twitcher/editor/inspector/user_property.gd b/addons/twitcher/editor/inspector/user_property.gd new file mode 100644 index 00000000..4bbe8cfa --- /dev/null +++ b/addons/twitcher/editor/inspector/user_property.gd @@ -0,0 +1,23 @@ +@tool +extends EditorProperty + +const USER_CONVERTER = preload("res://addons/twitcher/editor/inspector/user_converter.tscn") +const UserConverter = preload("res://addons/twitcher/editor/inspector/user_converter.gd") + +var _converter: UserConverter + + +func _init(): + _converter = USER_CONVERTER.instantiate() + _converter.changed.connect(_on_changed) + add_child(_converter) + + +func _update_property() -> void: + var user: TwitchUser = get_edited_object()[get_edited_property()] + _converter.update_user(user) + + +func _on_changed(user: TwitchUser) -> void: + emit_changed(get_edited_property(), user, &"", true) + _update_property() diff --git a/addons/twitcher/editor/inspector/user_property.gd.uid b/addons/twitcher/editor/inspector/user_property.gd.uid new file mode 100644 index 00000000..f8a4b02f --- /dev/null +++ b/addons/twitcher/editor/inspector/user_property.gd.uid @@ -0,0 +1 @@ +uid://c4ba54q6ecn7y diff --git a/addons/twitcher/editor/project_setting_property.gd b/addons/twitcher/editor/project_setting_property.gd new file mode 100644 index 00000000..697418ee --- /dev/null +++ b/addons/twitcher/editor/project_setting_property.gd @@ -0,0 +1,98 @@ +@tool +extends RefCounted + +class_name ProjectSettingProperty + +var key: String +var default_value: Variant + + +func _init(k: String, default_val: Variant = "") -> void: + key = k + default_value = default_val + _add_property() + + +func _add_property(): + if not ProjectSettings.has_setting(key): + ProjectSettings.set_setting(key, default_value) + ProjectSettings.set_initial_value(key, default_value) + + +func get_val() -> Variant: + return ProjectSettings.get_setting_with_override(key) + + +func set_val(val) -> void: + ProjectSettings.set(key, val) + + +func basic() -> ProjectSettingProperty: + ProjectSettings.set_as_basic(key, true) + return self + + +func as_str(description: String = "") -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_PLACEHOLDER_TEXT, description) + + +func as_select(values: Array[String], optional: bool = true) -> ProjectSettingProperty: + var hint_string = ",".join(values) + var enum_hint = PROPERTY_HINT_ENUM + if optional: enum_hint = PROPERTY_HINT_ENUM_SUGGESTION + return _add_type_def(TYPE_STRING, enum_hint, hint_string) + + +# Won't work in godot 4.4 the resource is not loaded when you select the project and it will just drop it out of the list. +#func as_resoruce(resource_name: StringName) -> ProjectSettingProperty: +# return _add_type_def(TYPE_OBJECT, PROPERTY_HINT_RESOURCE_TYPE, resource_name) + + +func as_bit_field(values: Array[String]) -> ProjectSettingProperty: + var hint_string = ",".join(values) + return _add_type_def(TYPE_INT, PROPERTY_HINT_FLAGS, hint_string) + + +func as_password(description: String = "") -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_PASSWORD, description) + + +func as_bool(description: String = "") -> ProjectSettingProperty: + return _add_type_def(TYPE_BOOL, PROPERTY_HINT_PLACEHOLDER_TEXT, description) + + +func as_num() -> ProjectSettingProperty: + return _add_type_def(TYPE_INT, PROPERTY_HINT_NONE, "") + + +func as_global() -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE, "") + + +## file_type is comma separated values "*.png,*.jpg,*.tres" +func as_file(file_type: String) -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_FILE, file_type) + + +func as_dir() -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_DIR, "") + + +## Type should be the generic type of the array +func as_list(type: Variant = "") -> ProjectSettingProperty: + return _add_type_def(TYPE_ARRAY, PROPERTY_HINT_ARRAY_TYPE, type) + + +## The hint string can be a set of filters with wildcards like "*.png,*.jpg" +func as_global_save(file_types: String = "") -> ProjectSettingProperty: + return _add_type_def(TYPE_STRING, PROPERTY_HINT_GLOBAL_SAVE_FILE, file_types) + + +func _add_type_def(type: int, hint: int, hint_string: Variant) -> ProjectSettingProperty: + ProjectSettings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + "hint_string": hint_string + }) + return self diff --git a/addons/twitcher/editor/project_setting_property.gd.uid b/addons/twitcher/editor/project_setting_property.gd.uid new file mode 100644 index 00000000..1d589e9e --- /dev/null +++ b/addons/twitcher/editor/project_setting_property.gd.uid @@ -0,0 +1 @@ +uid://cbmgm86618sxe diff --git a/addons/twitcher/editor/setup/file_select.gd b/addons/twitcher/editor/setup/file_select.gd new file mode 100644 index 00000000..a6297d51 --- /dev/null +++ b/addons/twitcher/editor/setup/file_select.gd @@ -0,0 +1,62 @@ +@tool +extends Control + +class_name FileSelect + +@export var default_path: String +@export var path: String: set = _update_filepath +@export var filters: PackedStringArray: set = _update_filters +@export var placeholder: String +@export var pick_folder: bool +@onready var line_edit: LineEdit = %LineEdit +@onready var button: Button = %Button +@onready var file_dialog: FileDialog = %FileDialog + +signal file_selected(path: String) + + +func _ready() -> void: + if Engine.is_editor_hint(): + var icon: Texture2D = EditorInterface.get_editor_theme().get_icon(&"FileBrowse", &"EditorIcons") + button.icon = icon + button.pressed.connect(_on_open_file_dialog) + file_dialog.file_selected.connect(_on_file_selected) + file_dialog.dir_selected.connect(_on_file_selected) + if pick_folder: file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR + line_edit.text_submitted.connect(_on_path_changed) + line_edit.placeholder_text = placeholder + + _update_filepath(path) + _update_filters(filters) + + file_dialog.current_path = path + + +func _update_filepath(new_path: String) -> void: + if new_path == null || new_path == "": + new_path = default_path + path = new_path + if not is_node_ready(): return + line_edit.text = path + + +func _update_filters(new_filters: PackedStringArray) -> void: + filters = new_filters + if is_inside_tree(): + file_dialog.filters = new_filters + + +func _on_open_file_dialog() -> void: + file_dialog.show() + + +func _on_path_changed(new_path: String) -> void: + file_dialog.current_path = new_path + path = new_path + file_selected.emit(new_path) + + +func _on_file_selected(new_path: String) -> void: + line_edit.text = new_path + path = new_path + file_selected.emit(new_path) diff --git a/addons/twitcher/editor/setup/file_select.gd.uid b/addons/twitcher/editor/setup/file_select.gd.uid new file mode 100644 index 00000000..a79bd699 --- /dev/null +++ b/addons/twitcher/editor/setup/file_select.gd.uid @@ -0,0 +1 @@ +uid://cgfc1nq4f4nae diff --git a/addons/twitcher/editor/setup/file_select.tscn b/addons/twitcher/editor/setup/file_select.tscn new file mode 100644 index 00000000..67d1c637 --- /dev/null +++ b/addons/twitcher/editor/setup/file_select.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=4 format=3 uid="uid://b7smp156mdns6"] + +[ext_resource type="Script" uid="uid://cgfc1nq4f4nae" path="res://addons/twitcher/editor/setup/file_select.gd" id="1_lnnjv"] + +[sub_resource type="Image" id="Image_lnnjv"] +data = { +"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 238, 225, 225, 225, 150, 225, 225, 225, 150, 224, 224, 224, 221, 224, 224, 224, 148, 224, 224, 224, 168, 224, 224, 224, 228, 224, 224, 224, 156, 224, 224, 224, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 238, 230, 230, 230, 30, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 150, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 153, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_7udr7"] +image = SubResource("Image_lnnjv") + +[node name="FileSelect" type="HBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +script = ExtResource("1_lnnjv") + +[node name="LineEdit" type="LineEdit" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Location where the OAuth Settings get saved to." + +[node name="Button" type="Button" parent="."] +unique_name_in_owner = true +layout_mode = 2 +icon = SubResource("ImageTexture_7udr7") + +[node name="FileDialog" type="FileDialog" parent="."] +unique_name_in_owner = true +auto_translate_mode = 1 +initial_position = 1 diff --git a/addons/twitcher/editor/setup/focus_child_show.gd b/addons/twitcher/editor/setup/focus_child_show.gd new file mode 100644 index 00000000..87842c85 --- /dev/null +++ b/addons/twitcher/editor/setup/focus_child_show.gd @@ -0,0 +1,24 @@ +extends Node + +@export var show_elements: Array[Control] = [] + + +func _ready() -> void: + for element: Control in show_elements: + element.hide() + + for child in get_children(): + if child.has_signal(&"focus_entered"): + child.connect(&"focus_entered", _on_focus_entered) + if child.has_signal(&"focus_exited"): + child.connect(&"focus_exited", _on_focus_exited) + + +func _on_focus_entered() -> void: + for element: Node in show_elements: + element.show() + + +func _on_focus_exited() -> void: + for element: Node in show_elements: + element.hide() diff --git a/addons/twitcher/editor/setup/focus_child_show.gd.uid b/addons/twitcher/editor/setup/focus_child_show.gd.uid new file mode 100644 index 00000000..24674afc --- /dev/null +++ b/addons/twitcher/editor/setup/focus_child_show.gd.uid @@ -0,0 +1 @@ +uid://ddugotjvuahex diff --git a/addons/twitcher/editor/setup/page_authorization.gd b/addons/twitcher/editor/setup/page_authorization.gd new file mode 100644 index 00000000..b5985003 --- /dev/null +++ b/addons/twitcher/editor/setup/page_authorization.gd @@ -0,0 +1,110 @@ +@tool +extends Node + +const TestCredentials = preload("res://addons/twitcher/editor/setup/test_credentials.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd") +const TWITCH_SERVICE: PackedScene = preload("res://addons/twitcher/twitch_service.tscn") + +@onready var authorization_explaination: RichTextLabel = %AuthExplain + +@onready var client_id: LineEdit = %ClientId +@onready var client_secret: LineEdit = %ClientSecret +@onready var redirect_url: LineEdit = %RedirectURL + +@onready var oauth_setting_file_select: FileSelect = %OauthSettingFileSelect +@onready var token_file_select: FileSelect = %TokenFileSelect + +@onready var to_documentation: Button = %ToDocumentation + +@onready var o_auth_save: Button = %OAuthSave +@onready var test_response: Label = %TestResponse +@onready var test_credentials: TestCredentials = %TestCredentials + +var has_changes: bool: + set(val): + has_changes = val + changed.emit.call_deferred() + o_auth_save.text = o_auth_save.text.trim_suffix(" (unsaved changes)") + if has_changes: o_auth_save.text += " (unsaved changes)" + +signal changed + + +func _ready() -> void: + authorization_explaination.meta_clicked.connect(_on_link_clicked) + + redirect_url.text_changed.connect(_on_text_changed) + client_id.text_changed.connect(_on_text_changed) + client_secret.text_changed.connect(_on_text_changed) + to_documentation.pressed.connect(_on_to_documentation_pressed) + oauth_setting_file_select.file_selected.connect(_on_file_changed) + token_file_select.file_selected.connect(_on_file_changed) + + o_auth_save.pressed.connect(_on_save) + + _load_oauth_setting() + + +func _load_oauth_setting() -> void: + var setting: OAuthSetting = TwitchEditorSettings.editor_oauth_setting + client_id.text = setting.client_id + client_secret.text = setting.get_client_secret() + redirect_url.text = setting.redirect_url + + oauth_setting_file_select.path = TwitchEditorSettings.game_oauth_setting.resource_path + token_file_select.path = TwitchEditorSettings.game_oauth_token.resource_path + + +func _on_link_clicked(link: Variant) -> void: + OS.shell_open(link) + + +func _on_text_changed(val: String) -> void: + reset_response_message() + var setting: OAuthSetting = TwitchEditorSettings.editor_oauth_setting + setting.client_id = client_id.text + setting.set_client_secret(client_secret.text) + setting.redirect_url = redirect_url.text + has_changes = true + + +func show_response_message(msg: String) -> void: + test_response.text = msg + + +func reset_response_message() -> void: + test_response.text = "" + + +func _on_file_changed() -> void: + has_changes = true + + +func is_auth_existing() -> bool: + return is_instance_valid(TwitchEditorSettings.editor_oauth_setting) + + +func _on_save() -> void: + TwitchEditorSettings.save_editor_oauth_setting() + TwitchEditorSettings.save_editor_oauth_token() + + var setting_path: String = oauth_setting_file_select.path + var setting: OAuthSetting = TwitchEditorSettings.editor_oauth_setting.duplicate(true) + setting.take_over_path(setting_path) + ResourceSaver.save(setting, setting_path) + TwitchEditorSettings.game_oauth_setting = setting + + var token_path: String = token_file_select.path + var token: OAuthToken = TwitchEditorSettings.editor_oauth_token.duplicate() + token.take_over_path(token_path) + ResourceSaver.save(token, token_path) + TwitchEditorSettings.game_oauth_token = token + + TwitchTweens.flash(o_auth_save, Color.GREEN) + ProjectSettings.save() + has_changes = false + + +func _on_to_documentation_pressed() -> void: + OS.shell_open("https://dev.twitch.tv/docs/authentication/") diff --git a/addons/twitcher/editor/setup/page_authorization.gd.uid b/addons/twitcher/editor/setup/page_authorization.gd.uid new file mode 100644 index 00000000..92d254dd --- /dev/null +++ b/addons/twitcher/editor/setup/page_authorization.gd.uid @@ -0,0 +1 @@ +uid://dxql15j5ornlc diff --git a/addons/twitcher/editor/setup/page_authorization.tscn b/addons/twitcher/editor/setup/page_authorization.tscn new file mode 100644 index 00000000..67b00920 --- /dev/null +++ b/addons/twitcher/editor/setup/page_authorization.tscn @@ -0,0 +1,183 @@ +[gd_scene load_steps=6 format=3 uid="uid://dm6jvnuikxtei"] + +[ext_resource type="Script" uid="uid://dxql15j5ornlc" path="res://addons/twitcher/editor/setup/page_authorization.gd" id="1_78hk7"] +[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/twitcher_title_label_settings.tres" id="2_owlil"] +[ext_resource type="PackedScene" uid="uid://b7smp156mdns6" path="res://addons/twitcher/editor/setup/file_select.tscn" id="3_dbhpx"] +[ext_resource type="Script" uid="uid://ddugotjvuahex" path="res://addons/twitcher/editor/setup/focus_child_show.gd" id="3_o4tdm"] +[ext_resource type="PackedScene" uid="uid://bfksyo3klyvdn" path="res://addons/twitcher/editor/setup/test_credentials.tscn" id="5_dbhpx"] + +[node name="Authorization" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 +script = ExtResource("1_78hk7") +metadata/_tab_index = 1 + +[node name="Layout" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Title" type="Label" parent="Layout"] +layout_mode = 2 +text = "Step 2: Authorization" +label_settings = ExtResource("2_owlil") +horizontal_alignment = 1 + +[node name="ToDocumentation" type="Button" parent="Layout/Title"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 6 +anchor_left = 1.0 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_left = -52.0 +offset_top = -15.5 +offset_bottom = 15.5 +grow_horizontal = 0 +grow_vertical = 2 +text = "DOCS" +metadata/_edit_use_anchors_ = true + +[node name="PanelContainer" type="PanelContainer" parent="Layout"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="Layout/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="Layout/PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="AuthorizationTitle" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "General" +label_settings = ExtResource("2_owlil") +horizontal_alignment = 1 + +[node name="AuthExplain" type="RichTextLabel" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 12 +bbcode_enabled = true +text = "The credentials that are used by the editor and game to connect to Twitch. You can request your credentials [url=https://dev.twitch.tv/console/apps/create]Twitch Dev Console[/url] for more informations see [url=https://twitcher.kani.dev/#authorization]Documentation[/url]" +fit_content = true +vertical_alignment = 1 + +[node name="AuthorizationOptions" type="GridContainer" parent="Layout/PanelContainer/MarginContainer/VBoxContainer" node_paths=PackedStringArray("show_elements")] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 3.0 +columns = 2 +script = ExtResource("3_o4tdm") +show_elements = [NodePath("../AuthExplain")] + +[node name="ClientIdLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +layout_mode = 2 +text = "Client ID:" + +[node name="ClientId" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "1ae0idgxbvn6vi97ls7d89cyd919oq" + +[node name="ClientSecretLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +layout_mode = 2 +text = "Client Secret:" + +[node name="ClientSecret" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "k22x037lmmrmkmwvy79xr19qfy993g" +secret = true + +[node name="RedirectURLLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +layout_mode = 2 +text = "Redirect URL:" + +[node name="RedirectURL" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Location that Twitch is calling after the login process. Take care that this one is the same that you used during creation of the application within the twitch dev console." +text = "http://localhost:7170" + +[node name="HSeparator2" type="HSeparator" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="GameSettingTitle" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Game" +label_settings = ExtResource("2_owlil") +horizontal_alignment = 1 + +[node name="GameExplain" type="RichTextLabel" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 12 +bbcode_enabled = true +text = "These settings are needed for the game to connect with Twitch." +fit_content = true +vertical_alignment = 1 + +[node name="GameSetting" type="GridContainer" parent="Layout/PanelContainer/MarginContainer/VBoxContainer" node_paths=PackedStringArray("show_elements")] +layout_mode = 2 +columns = 2 +script = ExtResource("3_o4tdm") +show_elements = [NodePath("../GameExplain")] + +[node name="OauthSettingLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting"] +layout_mode = 2 +text = "Auth File Path:" + +[node name="OauthSettingFileSelect" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting" instance=ExtResource("3_dbhpx")] +unique_name_in_owner = true +layout_mode = 2 +default_path = "res://addons/twitcher/twitch_oauth_setting.tres" +path = "res://addons/twitcher/twitch_oauth_setting.tres" +filters = PackedStringArray("*.tres", "*.res") + +[node name="TokenLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting"] +layout_mode = 2 +text = "Token File Path:" + +[node name="TokenFileSelect" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting" instance=ExtResource("3_dbhpx")] +unique_name_in_owner = true +layout_mode = 2 +default_path = "res://addons/twitcher/default_oauth_token.tres" +path = "res://addons/twitcher/default_oauth_token.tres" +filters = PackedStringArray("*.tres", "*.res") + +[node name="HSeparator" type="HSeparator" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="HBoxContainer" type="HBoxContainer" parent="Layout"] +layout_mode = 2 + +[node name="TestCredentials" parent="Layout/HBoxContainer" node_paths=PackedStringArray("test_response") instance=ExtResource("5_dbhpx")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +test_response = NodePath("../../TestResponse") + +[node name="OAuthSave" type="Button" parent="Layout/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Save" + +[node name="TestResponse" type="Label" parent="Layout"] +unique_name_in_owner = true +layout_mode = 2 diff --git a/addons/twitcher/editor/setup/page_use_case.gd b/addons/twitcher/editor/setup/page_use_case.gd new file mode 100644 index 00000000..71003ea6 --- /dev/null +++ b/addons/twitcher/editor/setup/page_use_case.gd @@ -0,0 +1,139 @@ +@tool +extends MarginContainer + +enum UseCase { + Overlay, Game, Other +} + +const PRESET_GAME_SCOPES = preload("res://addons/twitcher/auth/preset_game_scopes.tres") +const PRESET_OVERLAY_SCOPES = preload("res://addons/twitcher/auth/preset_overlay_scopes.tres") +const TwitchScopeProperty = preload("res://addons/twitcher/editor/inspector/twitch_scope_property.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd") + +@export var choose_button_group: ButtonGroup + +@onready var overlay: CheckBox = %Overlay +@onready var game: CheckBox = %Game +@onready var something_else: CheckBox = %SomethingElse +@onready var scope_list: RichTextLabel = %ScopeList +@onready var to_documentation: Button = %ToDocumentation +@onready var scope_file_select: FileSelect = %ScopeFileSelect +@onready var scopes_container: HBoxContainer = %Scopes +@onready var advanced_edit: CheckButton = %AdvancedEdit +@onready var other_scope_options: PanelContainer = %OtherScopeOptions +@onready var extended_scope_info: PanelContainer = %ExtendedScopeInfo +@onready var save: Button = %Save + +var other_scope_property: TwitchScopeProperty = TwitchScopeProperty.new() +var scopes: TwitchOAuthScopes: set = update_scopes +var has_changes: bool: + set(val): + has_changes = val + changed.emit() + save.text = save.text.trim_suffix(" (unsaved changes)") + if has_changes: save.text += " (unsaved changes)" + +signal changed +signal use_case_changed(use_case: UseCase) + + +func _ready() -> void: + to_documentation.pressed.connect(_on_to_documentation_pressed) + choose_button_group.pressed.connect(_on_choose) + scope_file_select.file_selected.connect(_on_scope_file_selected) + save.pressed.connect(_on_save_pressed) + other_scope_property.scope_selected.connect(_on_scope_info) + advanced_edit.toggled.connect(_on_toggle_advanced_edit) + + scopes_container.hide() + extended_scope_info.hide() + extended_scope_info.add_child(other_scope_property) + + # Reset radio buttons cause it's a tool script and the radio button stay and won't throw another signal otherwise + game.set_pressed_no_signal(false) + overlay.set_pressed_no_signal(false) + something_else.set_pressed_no_signal(false) + + scope_file_select.path = TwitchEditorSettings.get_scope_path() + match TwitchEditorSettings.project_preset: + TwitchEditorSettings.PRESET_GAME: + game.button_pressed = true + TwitchEditorSettings.PRESET_OVERLAY: + overlay.button_pressed = true + TwitchEditorSettings.PRESET_OTHER: + something_else.button_pressed = true + + # Needs to be resetted cause the radio reset will change the has_changges to true + has_changes = false + +func _on_scope_file_selected(path: String) -> void: + has_changes = true + if FileAccess.file_exists(path): + var resource = load(path) + if resource is OAuthScopes: scopes = resource + else: OS.alert("The selected scope is not a scope file, it will be overwritten!") + + +func _on_toggle_advanced_edit(toggled_on: bool) -> void: + extended_scope_info.visible = toggled_on + + +func _on_choose(button: BaseButton) -> void: + match button: + overlay: + use_case_changed.emit(UseCase.Overlay) + scopes = PRESET_OVERLAY_SCOPES.duplicate(true) + advanced_edit.button_pressed = false + TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_OVERLAY + game: + use_case_changed.emit(UseCase.Game) + scopes = PRESET_GAME_SCOPES.duplicate(true) + advanced_edit.button_pressed = false + TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_GAME + something_else: + use_case_changed.emit(UseCase.Other) + scopes = TwitchOAuthScopes.new() + advanced_edit.button_pressed = true + TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_OTHER + + _show_selected_scopes() + has_changes = true + +func _on_scope_info(scope: TwitchScope.Definition) -> void: + _show_selected_scopes() + + +func _on_save_pressed() -> void: + var s_path = scope_file_select.path + scopes.take_over_path(s_path) + ResourceSaver.save(scopes, s_path) + TwitchEditorSettings.set_scope_path(s_path) + TwitchTweens.flash(save, Color.GREEN) + has_changes = false + + +func _show_selected_scopes() -> void: + scopes_container.show() + + if scopes.used_scopes.is_empty(): + scope_list.text = "[i]No scopes selected yet[/i]" + return + + var scope_description: String = "" + for scope_name: StringName in scopes.used_scopes: + var scope: TwitchScope.Definition = TwitchScope.SCOPE_MAP[scope_name] + scope_description += "[b]%s[/b] - %s\n\n" % [scope.value, scope.description] + + scope_list.text = scope_description + + +func _on_to_documentation_pressed() -> void: + OS.shell_open("https://dev.twitch.tv/docs/authentication/scopes/") + + +func update_scopes(val: TwitchOAuthScopes) -> void: + scopes = val + other_scope_property.set_object_and_property(scopes, "") + other_scope_property.update_property() + _show_selected_scopes() diff --git a/addons/twitcher/editor/setup/page_use_case.gd.uid b/addons/twitcher/editor/setup/page_use_case.gd.uid new file mode 100644 index 00000000..12033406 --- /dev/null +++ b/addons/twitcher/editor/setup/page_use_case.gd.uid @@ -0,0 +1 @@ +uid://cjni881olloyf diff --git a/addons/twitcher/editor/setup/page_use_case.tscn b/addons/twitcher/editor/setup/page_use_case.tscn new file mode 100644 index 00000000..937e1552 --- /dev/null +++ b/addons/twitcher/editor/setup/page_use_case.tscn @@ -0,0 +1,162 @@ +[gd_scene load_steps=5 format=3 uid="uid://c7pja1druikbn"] + +[ext_resource type="Script" uid="uid://cjni881olloyf" path="res://addons/twitcher/editor/setup/page_use_case.gd" id="1_2qemh"] +[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/twitcher_title_label_settings.tres" id="1_r6qea"] +[ext_resource type="ButtonGroup" uid="uid://bkocyfdqvh4t" path="res://addons/twitcher/editor/setup/use_case_button_group.tres" id="1_vqr26"] +[ext_resource type="PackedScene" uid="uid://b7smp156mdns6" path="res://addons/twitcher/editor/setup/file_select.tscn" id="4_c6y6e"] + +[node name="UseCase" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 +script = ExtResource("1_2qemh") +choose_button_group = ExtResource("1_vqr26") +metadata/_tab_index = 0 + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 2 + +[node name="SelectionContainer" type="VBoxContainer" parent="ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Title" type="Label" parent="ScrollContainer/SelectionContainer"] +layout_mode = 2 +text = "Step 1: Use Case" +label_settings = ExtResource("1_r6qea") +horizontal_alignment = 1 + +[node name="ToDocumentation" type="Button" parent="ScrollContainer/SelectionContainer/Title"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 6 +anchor_left = 1.0 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_left = -52.0 +offset_top = -15.5 +offset_bottom = 15.5 +grow_horizontal = 0 +grow_vertical = 2 +text = "DOCS" +metadata/_edit_use_anchors_ = true + +[node name="Explaination" type="RichTextLabel" parent="ScrollContainer/SelectionContainer"] +layout_mode = 2 +theme_override_font_sizes/normal_font_size = 12 +bbcode_enabled = true +text = "To help you with scopes and authentication please select your use case." +fit_content = true +vertical_alignment = 1 + +[node name="ChooseLabel" type="Label" parent="ScrollContainer/SelectionContainer"] +layout_mode = 2 +text = "What do you want to make:" + +[node name="Overlay" type="CheckBox" parent="ScrollContainer/SelectionContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true +button_group = ExtResource("1_vqr26") +text = "Overlay" + +[node name="Game" type="CheckBox" parent="ScrollContainer/SelectionContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_group = ExtResource("1_vqr26") +text = "Game" + +[node name="SomethingElse" type="CheckBox" parent="ScrollContainer/SelectionContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_group = ExtResource("1_vqr26") +text = "I know what I do / Something else" + +[node name="HSeparator" type="HSeparator" parent="ScrollContainer/SelectionContainer"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="Scopes" type="HBoxContainer" parent="ScrollContainer/SelectionContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ExtendedScopeInfo" type="PanelContainer" parent="ScrollContainer/SelectionContainer/Scopes"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="OtherScopeOptions" type="PanelContainer" parent="ScrollContainer/SelectionContainer/Scopes"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions"] +layout_mode = 2 + +[node name="ScopeListLabel" type="Label" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"] +layout_mode = 2 +text = "Scopes:" +label_settings = ExtResource("1_r6qea") + +[node name="AdvancedEdit" type="CheckButton" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer/ScopeListLabel"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 6 +anchor_left = 1.0 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_left = -168.0 +offset_top = -15.5 +offset_bottom = 15.5 +grow_horizontal = 0 +grow_vertical = 2 +text = "Edit (Advanced)" + +[node name="ScopeList" type="RichTextLabel" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +bbcode_enabled = true +text = "[b]user:read:chat[/b] - Receive chatroom messages and informational notifications relating to a channel’s chatroom. + +[b]user:write:chat[/b] - Send chat messages to a chatroom. + +[b]moderator:read:followers[/b] - Read the followers of a broadcaster. + +[b]bits:read[/b] - View Bits information for a channel. + +[b]channel:read:redemptions[/b] - View Channel Points custom rewards and their redemptions on a channel. + +[b]channel:manage:redemptions[/b] - Manage Channel Points custom rewards and their redemptions on a channel. + +" +fit_content = true + +[node name="HSeparator" type="HSeparator" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="ScopeFileLabel" type="Label" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"] +layout_mode = 2 +text = "Save selected scopes:" + +[node name="ScopeFileSelect" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer" instance=ExtResource("4_c6y6e")] +unique_name_in_owner = true +layout_mode = 2 +default_path = "res://twitch_scopes.tres" +path = "res://twitch_scopes.tres" +filters = PackedStringArray("*.tres", "*.res") + +[node name="Save" type="Button" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Save Scopes" diff --git a/addons/twitcher/editor/setup/page_utilities.tscn b/addons/twitcher/editor/setup/page_utilities.tscn new file mode 100644 index 00000000..e18b346f --- /dev/null +++ b/addons/twitcher/editor/setup/page_utilities.tscn @@ -0,0 +1,61 @@ +[gd_scene load_steps=5 format=3 uid="uid://d4l63q706mkhw"] + +[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/twitcher_title_label_settings.tres" id="1_yi5sa"] +[ext_resource type="PackedScene" uid="uid://d2x3efon6jftb" path="res://addons/twitcher/editor/setup/section_reward.tscn" id="2_fj7h2"] +[ext_resource type="PackedScene" uid="uid://dapavtqnns1e3" path="res://addons/twitcher/editor/setup/section_autoload.tscn" id="3_0gkmy"] +[ext_resource type="Script" uid="uid://xha65i7thgq1" path="res://addons/twitcher/editor/setup/section_load_current_twitch_user.gd" id="4_0gkmy"] + +[node name="Utilities" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 2 + +[node name="Container" type="VBoxContainer" parent="ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Header" type="Label" parent="ScrollContainer/Container"] +layout_mode = 2 +text = "Utilities" +label_settings = ExtResource("1_yi5sa") +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="ScrollContainer/Container"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="SectionReward" parent="ScrollContainer/Container" instance=ExtResource("2_fj7h2")] +layout_mode = 2 + +[node name="HSeparator4" type="HSeparator" parent="ScrollContainer/Container"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="SectionAutoload" parent="ScrollContainer/Container" instance=ExtResource("3_0gkmy")] +layout_mode = 2 + +[node name="HSeparator2" type="HSeparator" parent="ScrollContainer/Container"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="LoadCurrentTwitchUser" type="CheckButton" parent="ScrollContainer/Container"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true +text = "Should TwitchUser Inspector automatically load the current user when empty?" +script = ExtResource("4_0gkmy") + +[node name="HSeparator3" type="HSeparator" parent="ScrollContainer/Container"] +layout_mode = 2 +theme_override_constants/separation = 20 diff --git a/addons/twitcher/editor/setup/section_autoload.gd b/addons/twitcher/editor/setup/section_autoload.gd new file mode 100644 index 00000000..da298b5f --- /dev/null +++ b/addons/twitcher/editor/setup/section_autoload.gd @@ -0,0 +1,77 @@ +@tool +extends Control + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + +const script_path: String = "res://addons/twitcher/twitch_service.tscn" +const autoload_name: String = "Twitch" +const setting_key: String = "autoload/%s" % autoload_name +const setting_value: String = "*" + script_path + +@onready var autoload_install: Button = %AutoloadInstall +@onready var autoload_info: Label = %AutoloadInfo +@onready var autoload_description: RichTextLabel = %AutoloadDescription + + +func _ready() -> void: + autoload_description.text = autoload_description.text.format({ + "autoload_name": autoload_name + }) + autoload_install.pressed.connect(_on_install_autoload_pressed) + _update_install_autoload() + + + +func _update_install_autoload() -> void: + if ProjectSettings.has_setting(setting_key): + autoload_install.text = "Uninstall Autoload" + else: + autoload_install.text = "Install Autoload" + + +func _on_install_autoload_pressed() -> void: + if ProjectSettings.has_setting(setting_key): + _uninstall_autoload() + else: + _install_autload() + _update_install_autoload() + + +func _uninstall_autoload() -> void: + ProjectSettings.clear(setting_key) + + var err = ProjectSettings.save() + + if err == OK: + autoload_info.text = "Autoload '%s' uninstalled successfully!\nYou might need to reload the current project for changes to fully apply everywhere in the editor immediately." % autoload_name + print("Successfully removed autoload: %s" % autoload_name) + else: + autoload_info.text = "Failed to save project settings.\nError code: %s" % error_string(err) + printerr("Failed to save project settings! Error: ", error_string(err)) + + +func _install_autload() -> void: + if not FileAccess.file_exists(script_path): + OS.alert("The TwitchService file does not exist at:\n" + script_path, "Error") + return + + var setting_key: String = "autoload/%s" % autoload_name + var setting_value: String = "*" + script_path + if ProjectSettings.has_setting(setting_key): + var existing_value = ProjectSettings.get_setting(setting_key) + if existing_value == setting_value: + autoload_info.text = "Autoload '%s' with the same path is already installed." % autoload_name + return + else: + autoload_info.text = "Autoload '%s' already exists but points to a different path (%s)." % [autoload_name, existing_value] + return + + ProjectSettings.set_setting(setting_key, setting_value) + var err = ProjectSettings.save() + + if err == OK: + autoload_info.text = "Autoload '%s' installed successfully!\nYou might need to reload the current project for changes to fully apply everywhere in the editor immediately." % autoload_name + print("Successfully added autoload: %s -> %s" % [autoload_name, script_path]) + else: + autoload_info.text = "Failed to save project settings.\nError code: %s" % error_string(err) + printerr("Failed to save project settings! Error: ", error_string(err)) diff --git a/addons/twitcher/editor/setup/section_autoload.gd.uid b/addons/twitcher/editor/setup/section_autoload.gd.uid new file mode 100644 index 00000000..6ceb8271 --- /dev/null +++ b/addons/twitcher/editor/setup/section_autoload.gd.uid @@ -0,0 +1 @@ +uid://dtvfyi2jygibd diff --git a/addons/twitcher/editor/setup/section_autoload.tscn b/addons/twitcher/editor/setup/section_autoload.tscn new file mode 100644 index 00000000..db5b5c2e --- /dev/null +++ b/addons/twitcher/editor/setup/section_autoload.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=3 format=3 uid="uid://dapavtqnns1e3"] + +[ext_resource type="Script" uid="uid://dtvfyi2jygibd" path="res://addons/twitcher/editor/setup/section_autoload.gd" id="1_jhiqg"] +[ext_resource type="LabelSettings" uid="uid://cng881nsuud80" path="res://addons/twitcher/assets/twitcher_warning_label_settings.tres" id="2_xfy68"] + +[node name="SectionAutoload" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_jhiqg") + +[node name="AutoloadDescription" type="RichTextLabel" parent="."] +unique_name_in_owner = true +layout_mode = 2 +bbcode_enabled = true +text = "For backwards compatibility, don't use it when you not actually need it! Adds an autoload named [b]Twitch[/b] you will need to modify the Scene with your configurations when you want to use Twitcher this way. + +[i]Advantage:[/i] ++ easy access everywhere +[i]Disadvantage:[/i] +- It will be always initialized, even when you want to test a small scene standalone Tiwtcher will do authorization, subscribing to eventsub etc. + +[b]Alternative:[/b] +The first nodes in the scene tree of every major nodes like TwitchAPI, TwitchEventsub, TwitchChat, TwitchMediaLoader and TwitchService are available as Singleton via their instance variable. + +Example +[code]TwitchAPI.instance.get_users(...)[/code]" +fit_content = true + +[node name="AutoloadInstall" type="Button" parent="."] +unique_name_in_owner = true +layout_mode = 2 +text = "Install Autoload" + +[node name="AutoloadInfo" type="Label" parent="."] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +label_settings = ExtResource("2_xfy68") +autowrap_mode = 3 diff --git a/addons/twitcher/editor/setup/section_load_current_twitch_user.gd b/addons/twitcher/editor/setup/section_load_current_twitch_user.gd new file mode 100644 index 00000000..06ee4d78 --- /dev/null +++ b/addons/twitcher/editor/setup/section_load_current_twitch_user.gd @@ -0,0 +1,12 @@ +@tool +extends CheckButton + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + +func _ready() -> void: + button_pressed = TwitchEditorSettings.load_current_twitch_user + toggled.connect(_on_toggle_load_current_twitch_user) + + +func _on_toggle_load_current_twitch_user(toggled_on: bool) -> void: + TwitchEditorSettings.load_current_twitch_user = toggled_on diff --git a/addons/twitcher/editor/setup/section_load_current_twitch_user.gd.uid b/addons/twitcher/editor/setup/section_load_current_twitch_user.gd.uid new file mode 100644 index 00000000..df39ed82 --- /dev/null +++ b/addons/twitcher/editor/setup/section_load_current_twitch_user.gd.uid @@ -0,0 +1 @@ +uid://xha65i7thgq1 diff --git a/addons/twitcher/editor/setup/section_reward.gd b/addons/twitcher/editor/setup/section_reward.gd new file mode 100644 index 00000000..f6e43f1f --- /dev/null +++ b/addons/twitcher/editor/setup/section_reward.gd @@ -0,0 +1,76 @@ +@tool +extends Control + +const TwitchRewardEditorService = preload("res://addons/twitcher/editor/inspector/twitch_reward_editor_service.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchEditorNodeUtils = preload("res://addons/twitcher/editor/twitch_editor_node_utils.gd") + +@onready var file_select: FileSelect = %FileSelect +@onready var fetch_all_rewards: Button = %FetchAllRewards +@onready var fetch_manageable_rewards: Button = %FetchManageableRewards +@onready var info: Label = %Info + +@export var token: OAuthToken +@export var setting: OAuthSetting + +func _ready() -> void: + fetch_all_rewards.pressed.connect(_on_fetch_all) + fetch_manageable_rewards.pressed.connect(_on_fetch_manageable) + file_select.file_selected.connect(_on_file_selected) + file_select.path = TwitchEditorSettings.reward_folder + + +func _on_file_selected(path: String) -> void: + TwitchEditorSettings.reward_folder = path + + +func _download_rewards(all: bool) -> void: + var api: TwitchAPI = TwitchEditorNodeUtils.create_api(token, setting) + add_child(api) + var media_loader: TwitchMediaLoader = TwitchEditorNodeUtils.create_media_loader(api) + add_child(media_loader) + var broadcaster: TwitchUser = await TwitchService.get_current_user_via_api(api) + var rewards: Array[TwitchCustomReward] = await _fetch_rewards(api, all, broadcaster) + var twitch_rewards: Array[TwitchReward] = [] + for reward in rewards: + var twitch_reward: TwitchReward = TwitchReward.new() + twitch_reward.broadcaster_user = broadcaster + TwitchRewardEditorService.convert_to_twitch_reward(twitch_reward, reward, media_loader) + var file_name: String = twitch_reward.title.to_snake_case() + file_name = RegEx.create_from_string("[^\\w_-]").sub(file_name, "") + var folder: String = file_select.path + if not folder: folder = "res://" + printt(twitch_reward.title, twitch_reward.id) + + ResourceSaver.save(twitch_reward, folder + "/" + file_name + ".tres") + media_loader.queue_free() + api.queue_free() + + +func _fetch_rewards(api: TwitchAPI, all: bool, broadcaster: TwitchUser) -> Array[TwitchCustomReward]: + if not TwitchEditorSettings.is_valid(): + _info("Editor is not authorizd yet. Use 'Test Credentials' from the previous page to authorize the editor.") + TwitchEditorSettings.editor_oauth_token.authorized.connect(func(): _info(""), CONNECT_ONE_SHOT) + return [] + + + var opt: TwitchGetCustomReward.Opt = TwitchGetCustomReward.Opt.create() + opt.only_manageable_rewards = not all + var response: TwitchGetCustomReward.Response = await api.get_custom_reward(opt, broadcaster.id) + if response.response.response_code == 200: + return response.data + var response_data: String = response.response.response_data.get_string_from_utf8() + _info("Couldn't fetch Rewards cause of %s" % response_data) + return [] + + +func _info(text: String) -> void: + info.text = text + + +func _on_fetch_all() -> void: + _download_rewards(true) + + +func _on_fetch_manageable() -> void: + _download_rewards(false) diff --git a/addons/twitcher/editor/setup/section_reward.gd.uid b/addons/twitcher/editor/setup/section_reward.gd.uid new file mode 100644 index 00000000..328ea09c --- /dev/null +++ b/addons/twitcher/editor/setup/section_reward.gd.uid @@ -0,0 +1 @@ +uid://byv7ndmkuf3pf diff --git a/addons/twitcher/editor/setup/section_reward.tscn b/addons/twitcher/editor/setup/section_reward.tscn new file mode 100644 index 00000000..bb8921e2 --- /dev/null +++ b/addons/twitcher/editor/setup/section_reward.tscn @@ -0,0 +1,46 @@ +[gd_scene load_steps=3 format=3 uid="uid://d2x3efon6jftb"] + +[ext_resource type="PackedScene" uid="uid://b7smp156mdns6" path="res://addons/twitcher/editor/setup/file_select.tscn" id="1_i70mb"] +[ext_resource type="Script" uid="uid://byv7ndmkuf3pf" path="res://addons/twitcher/editor/setup/section_reward.gd" id="1_p2e20"] + +[node name="SectionReward" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_p2e20") + +[node name="Label" type="RichTextLabel" parent="."] +layout_mode = 2 +bbcode_enabled = true +text = "You can download all the Channel Point Rewards as Resources for easier maintaining / easier access to the ID etc. +[i]Info: Only rewards created with this application can be updated here. [b]All other rewards are readonly.[/b][/i]" +fit_content = true + +[node name="FileSelect" parent="." instance=ExtResource("1_i70mb")] +unique_name_in_owner = true +layout_mode = 2 +default_path = "res://" +path = "res://tests/rewards" +placeholder = "Target folder for Reward Resources" +pick_folder = true + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="FetchAllRewards" type="Button" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Fetch All Rewards" + +[node name="FetchManageableRewards" type="Button" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "Fetch Own Rewards" + +[node name="Info" type="Label" parent="."] +unique_name_in_owner = true +layout_mode = 2 diff --git a/addons/twitcher/editor/setup/setup.gd b/addons/twitcher/editor/setup/setup.gd new file mode 100644 index 00000000..4fe28a6b --- /dev/null +++ b/addons/twitcher/editor/setup/setup.gd @@ -0,0 +1,58 @@ +@tool +extends Window + +const PageUseCase = preload("res://addons/twitcher/editor/setup/page_use_case.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const PageAuthorization = preload("res://addons/twitcher/editor/setup/page_authorization.gd") + +#Setup +#- Check for Authorization Stuff +#-- Client Credentials +#-- Editor Token +#-- Scopes +#- Auth Button +#- Create Base Node Structure + +@onready var authorization: PageAuthorization = %Authorization +@onready var use_case: PageUseCase = %UseCase as PageUseCase +@onready var close: Button = %Close +@onready var startup_check: CheckButton = %StartupCheck + + +func _ready(): + close_requested.connect(_on_close) + close.pressed.connect(_on_close) + startup_check.toggled.connect(_on_toggle_startup_check) + startup_check.button_pressed = TwitchEditorSettings.show_setup_on_startup + use_case.changed.connect(_on_changed) + authorization.changed.connect(_on_changed) + pass + + +func _on_changed() -> void: + close.text = close.text.trim_suffix(" (unsaved changes)") + if use_case.has_changes || authorization.has_changes: + close.text = close.text + " (unsaved changes)" + + +func _on_toggle_startup_check(toggle_on: bool) -> void: + TwitchEditorSettings.show_setup_on_startup = toggle_on + ProjectSettings.save() + + +func _input(event: InputEvent) -> void: + if event is InputEventKey: + var key_event: InputEventKey = event as InputEventKey + if key_event.keycode == KEY_ESCAPE: + _on_close() + + +func _on_close() -> void: + if use_case.has_changes || authorization.has_changes: + var popup = ConfirmationDialog.new() + popup.dialog_text = "You have unsaved changes! Are you sure to close the setup?" + popup.confirmed.connect(queue_free) + add_child(popup) + popup.popup_centered() + else: + queue_free() diff --git a/addons/twitcher/editor/setup/setup.gd.uid b/addons/twitcher/editor/setup/setup.gd.uid new file mode 100644 index 00000000..2ad7486b --- /dev/null +++ b/addons/twitcher/editor/setup/setup.gd.uid @@ -0,0 +1 @@ +uid://bbguje3a0cl8t diff --git a/addons/twitcher/editor/setup/setup.tscn b/addons/twitcher/editor/setup/setup.tscn new file mode 100644 index 00000000..d5f40d64 --- /dev/null +++ b/addons/twitcher/editor/setup/setup.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=6 format=3 uid="uid://wu1fprbhr62"] + +[ext_resource type="Script" uid="uid://bbguje3a0cl8t" path="res://addons/twitcher/editor/setup/setup.gd" id="1_o5snq"] +[ext_resource type="PackedScene" uid="uid://c7pja1druikbn" path="res://addons/twitcher/editor/setup/page_use_case.tscn" id="2_6678v"] +[ext_resource type="PackedScene" uid="uid://dm6jvnuikxtei" path="res://addons/twitcher/editor/setup/page_authorization.tscn" id="3_qcivh"] +[ext_resource type="PackedScene" uid="uid://d4l63q706mkhw" path="res://addons/twitcher/editor/setup/page_utilities.tscn" id="4_qcivh"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_6678v"] + +[node name="SetupWindow" type="Window"] +title = "Setup Twitcher" +initial_position = 2 +size = Vector2i(800, 800) +script = ExtResource("1_o5snq") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Setup" type="TabContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="UseCase" parent="VBoxContainer/Setup" instance=ExtResource("2_6678v")] +unique_name_in_owner = true +layout_mode = 2 +choose_button_group = SubResource("ButtonGroup_6678v") + +[node name="Authorization" parent="VBoxContainer/Setup" instance=ExtResource("3_qcivh")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="Utilities" parent="VBoxContainer/Setup" instance=ExtResource("4_qcivh")] +visible = false +layout_mode = 2 + +[node name="StartupCheck" type="CheckButton" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true +text = "Show on startup (you can open it via 'Project/Tools/Twitcher Setup')" + +[node name="Close" type="Button" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Close" diff --git a/addons/twitcher/editor/setup/test_credentials.gd b/addons/twitcher/editor/setup/test_credentials.gd new file mode 100644 index 00000000..a577b3fe --- /dev/null +++ b/addons/twitcher/editor/setup/test_credentials.gd @@ -0,0 +1,76 @@ +@tool +extends Button + +const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + +@export var oauth_setting: OAuthSetting: set = update_oauth_setting +@export var oauth_token: OAuthToken: set = update_oauth_token +@export var test_response: Label + +@onready var twitch_auth: TwitchAuth = %TwitchAuth +@onready var o_auth: OAuth = %OAuth +@onready var token_handler: TwitchTokenHandler = %TokenHandler + +signal authorized + +func _ready() -> void: + pressed.connect(_pressed) + oauth_setting = TwitchEditorSettings.editor_oauth_setting + oauth_token = TwitchEditorSettings.editor_oauth_token + + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + oauth_token.authorized.disconnect(_on_authorized) + + +func _pressed() -> void: + if test_response: + _set_test_response("Authorizing...") + if o_auth.login_in_process: + _set_test_response("Another login trial is in process. Wait for timeout!", Color.YELLOW) + if await token_handler.token_resolved == null: + _set_test_response("Login unsuccessful!", Color.RED) + + TwitchTweens.loading(self) + await twitch_auth.authorize() + + if twitch_auth.token.is_token_valid(): + _set_test_response("Credentials are valid!", Color.GREEN) + TwitchTweens.flash(self, Color.GREEN) + authorized.emit() + else: + _set_test_response("Credentials are invalid!", Color.RED) + TwitchTweens.flash(self, Color.RED) + + +func _set_test_response(info: String, color: Color = Color.TRANSPARENT) -> void: + if test_response: + test_response.text = info + if color == Color.TRANSPARENT: + test_response.remove_theme_color_override(&"font_color") + else: + test_response.add_theme_color_override(&"font_color", color) + + +func update_oauth_token(new_oauth_token: OAuthToken) -> void: + if oauth_token && oauth_token.authorized.is_connected(_on_authorized): + oauth_token.authorized.disconnect(_on_authorized) + + oauth_token = new_oauth_token + if not oauth_token.authorized.is_connected(_on_authorized): + oauth_token.authorized.connect(_on_authorized) + if is_inside_tree(): + twitch_auth.token = new_oauth_token + + +func update_oauth_setting(new_oauth_setting: OAuthSetting) -> void: + oauth_setting = new_oauth_setting + disabled = not oauth_setting.is_valid() + if is_inside_tree(): + twitch_auth.oauth_setting = oauth_setting + + +func _on_authorized() -> void: + if is_inside_tree(): authorized.emit() diff --git a/addons/twitcher/editor/setup/test_credentials.gd.uid b/addons/twitcher/editor/setup/test_credentials.gd.uid new file mode 100644 index 00000000..7107e867 --- /dev/null +++ b/addons/twitcher/editor/setup/test_credentials.gd.uid @@ -0,0 +1 @@ +uid://13afcys4swos diff --git a/addons/twitcher/editor/setup/test_credentials.tscn b/addons/twitcher/editor/setup/test_credentials.tscn new file mode 100644 index 00000000..51b09202 --- /dev/null +++ b/addons/twitcher/editor/setup/test_credentials.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=8 format=3 uid="uid://bfksyo3klyvdn"] + +[ext_resource type="Script" uid="uid://13afcys4swos" path="res://addons/twitcher/editor/setup/test_credentials.gd" id="1_j2v3o"] +[ext_resource type="Script" uid="uid://iv0mgv0lu8b0" path="res://addons/twitcher/auth/twitch_auth.gd" id="1_kojf4"] +[ext_resource type="Resource" path="user://editor_oauth_setting.tres" id="3_kojf4"] +[ext_resource type="Resource" path="user://editor_oauth_token.tres" id="4_j2v3o"] +[ext_resource type="Resource" uid="uid://cgqldyna2cv5h" path="res://addons/twitcher/assets/twitcher_editor_scopes.tres" id="5_kojf4"] +[ext_resource type="Script" uid="uid://bf0wi70haua35" path="res://addons/twitcher/lib/oOuch/oauth.gd" id="6_hkawa"] +[ext_resource type="Script" uid="uid://blnbogtrshw4r" path="res://addons/twitcher/auth/twitch_token_handler.gd" id="7_v5ghs"] + +[node name="TestCredentials" type="Button"] +text = "Authorized Editor" +script = ExtResource("1_j2v3o") +oauth_setting = ExtResource("3_kojf4") +oauth_token = ExtResource("4_j2v3o") + +[node name="TwitchAuth" type="Node" parent="."] +unique_name_in_owner = true +script = ExtResource("1_kojf4") +oauth_setting = ExtResource("3_kojf4") +token = ExtResource("4_j2v3o") +scopes = ExtResource("5_kojf4") +metadata/_custom_type_script = "uid://iv0mgv0lu8b0" + +[node name="OAuth" type="Node" parent="TwitchAuth" node_paths=PackedStringArray("token_handler")] +unique_name_in_owner = true +script = ExtResource("6_hkawa") +oauth_setting = ExtResource("3_kojf4") +scopes = ExtResource("5_kojf4") +token_handler = NodePath("../TokenHandler") + +[node name="TokenHandler" type="Node" parent="TwitchAuth"] +unique_name_in_owner = true +script = ExtResource("7_v5ghs") +oauth_setting = ExtResource("3_kojf4") +token = ExtResource("4_j2v3o") diff --git a/addons/twitcher/editor/setup/use_case_button_group.tres b/addons/twitcher/editor/setup/use_case_button_group.tres new file mode 100644 index 00000000..aecc11fb --- /dev/null +++ b/addons/twitcher/editor/setup/use_case_button_group.tres @@ -0,0 +1,3 @@ +[gd_resource type="ButtonGroup" format=3 uid="uid://bkocyfdqvh4t"] + +[resource] diff --git a/addons/twitcher/editor/twitch_editor_node_utils.gd b/addons/twitcher/editor/twitch_editor_node_utils.gd new file mode 100644 index 00000000..11074cef --- /dev/null +++ b/addons/twitcher/editor/twitch_editor_node_utils.gd @@ -0,0 +1,20 @@ +@tool +extends Object + +# It prevent manual tempering with editor settings while the inspector is shown +# and ensure that always the latest auth token and oauth settings will be loaded + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") + + +static func create_api(token: OAuthToken, setting: OAuthSetting) -> TwitchAPI: + var api: TwitchAPI = TwitchAPI.new() + api.token = token if token else TwitchEditorSettings.editor_oauth_token + api.oauth_setting = setting if setting else TwitchEditorSettings.editor_oauth_setting + return api + + +static func create_media_loader(api: TwitchAPI) -> TwitchMediaLoader: + var media_loader: TwitchMediaLoader = TwitchMediaLoader.new() + media_loader.api = api + return media_loader diff --git a/addons/twitcher/editor/twitch_editor_node_utils.gd.uid b/addons/twitcher/editor/twitch_editor_node_utils.gd.uid new file mode 100644 index 00000000..d11be023 --- /dev/null +++ b/addons/twitcher/editor/twitch_editor_node_utils.gd.uid @@ -0,0 +1 @@ +uid://cwvdheqhq5yrl diff --git a/addons/twitcher/editor/twitch_editor_settings.gd b/addons/twitcher/editor/twitch_editor_settings.gd new file mode 100644 index 00000000..2d03dcdf --- /dev/null +++ b/addons/twitcher/editor/twitch_editor_settings.gd @@ -0,0 +1,171 @@ +@tool +extends RefCounted + +## Utilitiy Node for Inspector to calling API functionallity at any point + +const EDITOR_OAUTH_TOKEN: String = "user://editor_oauth_token.tres" +const EDITOR_OAUTH_SETTING: String = "user://editor_oauth_setting.tres" +const GAME_OAUTH_TOKEN: String = "res://twitch_oauth_token.tres" +const GAME_OAUTH_SETTING: String = "res://twitch_oauth_setting.tres" +const TWITCH_DEFAULT_SCOPE: String = "res://addons/twitcher/auth/preset_overlay_scopes.tres" + +const PRESET_GAME: StringName = &"Game" +const PRESET_OVERLAY: StringName = &"Overlay" +const PRESET_OTHER: StringName = &"Other" + +static var _log : TwitchLogger = TwitchLogger.new("TwitchEditorSetting") + +static var _editor_oauth_token_property: ProjectSettingProperty +static var editor_oauth_token: OAuthToken: + set(val): + editor_oauth_token = val + if not _reloading: _editor_oauth_token_property.set_val(val.resource_path) + +static var _editor_oauth_setting_property: ProjectSettingProperty +static var editor_oauth_setting: OAuthSetting: + set(val): + editor_oauth_setting = val + if not _reloading: _editor_oauth_setting_property.set_val(val.resource_path) + +static var _game_oauth_token_property: ProjectSettingProperty +static var game_oauth_token: OAuthToken: + set(val): + game_oauth_token = val + if not _reloading: _game_oauth_token_property.set_val(val.resource_path) + +static var _game_oauth_setting_property: ProjectSettingProperty +static var game_oauth_setting: OAuthSetting: + set(val): + game_oauth_setting = val + if not _reloading: _game_oauth_setting_property.set_val(val.resource_path) + +static var _scope_property: ProjectSettingProperty +static var scopes: TwitchOAuthScopes: + set(val): + scopes = val + if not _reloading: _scope_property.set_val(val.resource_path) + +static var _show_setup_on_startup: ProjectSettingProperty +static var show_setup_on_startup: bool: + set(val): _show_setup_on_startup.set_val(val) + get: return _show_setup_on_startup.get_val() + +static var _project_preset: ProjectSettingProperty +static var project_preset: StringName: + set(val): _project_preset.set_val(val) + get: return _project_preset.get_val() + +static var _load_current_twitch_user: ProjectSettingProperty +static var load_current_twitch_user: bool: + set(val): _load_current_twitch_user.set_val(val) + get: return _load_current_twitch_user.get_val() + +static var _reward_folder: ProjectSettingProperty +static var reward_folder: String: + set(val): _reward_folder.set_val(val) + get: return _reward_folder.get_val() + +static var _initialized: bool +static var _reloading: bool + + +static func setup() -> void: + if not _initialized: + _initialized = true + _setup_project_settings() + _reload_setting() + ProjectSettings.settings_changed.connect(_reload_setting) + + +static func _setup_project_settings() -> void: + _editor_oauth_token_property = ProjectSettingProperty.new("twitcher/editor/editor_oauth_token", EDITOR_OAUTH_TOKEN) + _editor_oauth_token_property.as_file("*.res,*.tres") + + _editor_oauth_setting_property = ProjectSettingProperty.new("twitcher/editor/editor_oauth_setting", EDITOR_OAUTH_SETTING) + _editor_oauth_setting_property.as_file("*.res,*.tres") + + _game_oauth_token_property = ProjectSettingProperty.new("twitcher/editor/game_oauth_token", GAME_OAUTH_TOKEN) + _game_oauth_token_property.as_file("*.res,*.tres") + + _game_oauth_setting_property = ProjectSettingProperty.new("twitcher/editor/game_oauth_setting", GAME_OAUTH_SETTING) + _game_oauth_setting_property.as_file("*.res,*.tres") + + _scope_property = ProjectSettingProperty.new("twitcher/editor/default_scopes", TWITCH_DEFAULT_SCOPE) + _scope_property.as_file("*.res,*.tres") + + _show_setup_on_startup = ProjectSettingProperty.new("twitcher/editor/show_setup_on_startup", true) + + _project_preset = ProjectSettingProperty.new("twitcher/editor/project_preset") + _project_preset.as_select([PRESET_GAME, PRESET_OVERLAY, PRESET_OTHER], false) + + _load_current_twitch_user = ProjectSettingProperty.new("twitcher/editor/load_current_twitch_user", true) + _load_current_twitch_user.as_bool("Should always load the current user when converting to TwitchUser") + + _reward_folder = ProjectSettingProperty.new("twitcher/editor/reward_folder", "res://") + _reward_folder.as_dir() + + +static func _reload_setting() -> void: + _reloading = true + editor_oauth_setting = load(_editor_oauth_setting_property.get_val()) + editor_oauth_token = load(_editor_oauth_token_property.get_val()) + game_oauth_setting = load(_game_oauth_setting_property.get_val()) + game_oauth_token = load(_game_oauth_token_property.get_val()) + + var editor_oauth_token_path: String = _editor_oauth_token_property.get_val() + if editor_oauth_token_path: + if not FileAccess.file_exists(editor_oauth_token_path): + _create_editor_oauth_token() + + var editor_oauth_setting_path: String = _editor_oauth_setting_property.get_val() + if editor_oauth_setting_path: + if not FileAccess.file_exists(editor_oauth_setting_path): + _create_editor_oauth_setting() + + var scope_path: String = get_scope_path() + if scope_path and FileAccess.file_exists(scope_path): + scopes = load(scope_path) + _reloading = false + + +static func save_editor_oauth_setting() -> void: + _log.d("Saves editor oauth setting") + ResourceSaver.save(editor_oauth_setting) + + +static func save_editor_oauth_token() -> void: + _log.d("Saves editor oauth token") + ResourceSaver.save(editor_oauth_token) + + +static func get_scope_path() -> String: + return _scope_property.get_val() + + +static func set_scope_path(path: String) -> void: + _scope_property.set_val(path) + + +static func is_valid() -> bool: + var token_valid: bool = is_instance_valid(editor_oauth_token) && editor_oauth_token.is_token_valid() + var setting_valid : bool = is_instance_valid(editor_oauth_setting) && editor_oauth_setting.is_valid() + return token_valid && setting_valid + + +static func _create_editor_oauth_token() -> void: + _log.i("Create new Editor Token resource") + var path: String = _editor_oauth_token_property.get_val() + var token = OAuthToken.new() + token._identifier = "EditorToken" + token.take_over_path(path) + editor_oauth_token = token + save_editor_oauth_token() + + +static func _create_editor_oauth_setting() -> void: + _log.i("Create new Editor Oauth settings") + var path: String = _editor_oauth_setting_property.get_val() + var setting: OAuthSetting = TwitchAuth.create_default_oauth_setting() + setting.take_over_path(path) + editor_oauth_setting = setting + save_editor_oauth_setting() diff --git a/addons/twitcher/editor/twitch_editor_settings.gd.uid b/addons/twitcher/editor/twitch_editor_settings.gd.uid new file mode 100644 index 00000000..3767d52f --- /dev/null +++ b/addons/twitcher/editor/twitch_editor_settings.gd.uid @@ -0,0 +1 @@ +uid://kqcukq2xqnuf diff --git a/addons/twitcher/editor/twitch_tweens.gd b/addons/twitcher/editor/twitch_tweens.gd new file mode 100644 index 00000000..633320c4 --- /dev/null +++ b/addons/twitcher/editor/twitch_tweens.gd @@ -0,0 +1,17 @@ +static func flash(object: Control, color: Color, duration: float = .25) -> void: + var tween = object.create_tween() + tween.tween_property(object, ^"modulate", color, duration)\ + .set_ease(Tween.EASE_OUT)\ + .set_trans(Tween.TRANS_CIRC) + tween.tween_property(object, ^"modulate", Color.WHITE, duration)\ + .set_ease(Tween.EASE_OUT)\ + .set_trans(Tween.TRANS_CIRC) + await tween.finished + + +static func loading(object: Control, color: Color = Color.YELLOW) -> void: + var tween: Tween = object.create_tween() + tween.tween_property(object, ^"modulate", color, 0.2) \ + .set_trans(Tween.TRANS_LINEAR) \ + .set_ease(Tween.EASE_IN_OUT) + await tween.finished diff --git a/addons/twitcher/editor/twitch_tweens.gd.uid b/addons/twitcher/editor/twitch_tweens.gd.uid new file mode 100644 index 00000000..bf15a56b --- /dev/null +++ b/addons/twitcher/editor/twitch_tweens.gd.uid @@ -0,0 +1 @@ +uid://duhsr84u352ef diff --git a/addons/twitcher/eventsub/twitch_event_listener.gd b/addons/twitcher/eventsub/twitch_event_listener.gd new file mode 100644 index 00000000..a5245cce --- /dev/null +++ b/addons/twitcher/eventsub/twitch_event_listener.gd @@ -0,0 +1,73 @@ +@tool +@icon("../assets/event-icon.svg") +extends Twitcher + +## Listens to an event and publishes it as signal. +## Usage for easy access of events on test and normal eventsub makes it more obvious what a scene +## is listening before diving in the code. + +## [b]Expects that the signal was already configured in the eventsub or manually subscribed[/b] +class_name TwitchEventListener +static var _log : TwitchLogger = TwitchLogger.new("TwitchEventListener") + +@export var eventsub: TwitchEventsub: + set = _update_eventsub + +@export var subscription: TwitchEventsubDefinition.Type: + set(val): + subscription = val + subscription_definition = TwitchEventsubDefinition.ALL[subscription] + update_configuration_warnings() + +var subscription_definition: TwitchEventsubDefinition + + +## Called when the event got received +signal received(data: Dictionary) + + +func _ready() -> void: + if eventsub == null: eventsub = TwitchEventsub.instance + _update_eventsub(eventsub) + + +func _enter_tree() -> void: + start_listening() + + +func _exit_tree() -> void: + stop_listening() + + +func start_listening() -> void: + _log.d("start listening %s" % subscription_definition.get_readable_name()) + if eventsub != null && not eventsub.event.is_connected(_on_received): + eventsub.event.connect(_on_received) + + +func stop_listening() -> void: + _log.d("stop listening %s" % subscription_definition.get_readable_name()) + if eventsub != null && eventsub.event.is_connected(_on_received): + eventsub.event.disconnect(_on_received) + + +func _update_eventsub(val: TwitchEventsub): + stop_listening() + eventsub = val + update_configuration_warnings() + if val == null: return + start_listening() + + +func _on_received(type: String, data: Dictionary): + if type == subscription_definition.value: + received.emit(data) + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if eventsub == null: + result.append("'EventSub' is missing") + if subscription == null: + result.append("'Subscription' is missing") + return result diff --git a/addons/twitcher/eventsub/twitch_event_listener.gd.uid b/addons/twitcher/eventsub/twitch_event_listener.gd.uid new file mode 100644 index 00000000..d21a97db --- /dev/null +++ b/addons/twitcher/eventsub/twitch_event_listener.gd.uid @@ -0,0 +1 @@ +uid://bol5ltrjr6ux8 diff --git a/addons/twitcher/eventsub/twitch_eventsub.gd b/addons/twitcher/eventsub/twitch_eventsub.gd new file mode 100644 index 00000000..f93d5fb2 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub.gd @@ -0,0 +1,398 @@ +@icon("res://addons/twitcher/assets/eventsub-icon.svg") +@tool +extends Twitcher + +## Handles the evensub part of twitch. Returns the event data when receives it. +class_name TwitchEventsub + +static var _log: TwitchLogger = TwitchLogger.new("TwitchEventsub") + +static var instance: TwitchEventsub + +## An object that identifies the message. +class Metadata extends RefCounted: + ## An ID that uniquely identifies the message. Twitch sends messages at least once, but if Twitch is unsure of whether you received a notification, it’ll resend the message. This means you may receive a notification twice. If Twitch resends the message, the message ID is the same. + var message_id: String + ## The type of message, which is set to session_keepalive. + var message_type: String + ## The UTC date and time that the message was sent. + var message_timestamp: String + + func _init(d: Dictionary): + message_id = d['message_id'] + message_type = d['message_type'] + message_timestamp = d['message_timestamp'] + + +## An object that contains information about the connection. +class Session extends RefCounted: + ## An ID that uniquely identifies this WebSocket connection. Use this ID to set the session_id field in all subscription requests. + var id: String + ## The connection’s status, which is set to connected. + var status: String + ## The maximum number of seconds that you should expect silence before receiving a keepalive message. For a welcome message, this is the number of seconds that you have to subscribe to an event after receiving the welcome message. If you don’t subscribe to an event within this window, the socket is disconnected. + var keepalive_timeout_seconds: int + ## The URL to reconnect to if you get a Reconnect message. Is set to null. + var reconnect_url: String + ## The UTC date and time that the connection was created. + var connected_at: String + + func _init(d: Dictionary): + id = d["id"] + status = d["status"] + var timeout = d["keepalive_timeout_seconds"] + keepalive_timeout_seconds = timeout if timeout != null else 30 + if d["reconnect_url"] != null: + reconnect_url = d["reconnect_url"] + connected_at = d["connected_at"] + +## A specific event received from eventsub +class Event extends RefCounted: + var type: TwitchEventsubDefinition: + get(): return TwitchEventsubDefinition.BY_NAME[message.payload.subscription.type] + var data: Dictionary: + get(): return message.payload.event + var message: TwitchNotificationMessage + + func _init(notification_message: TwitchNotificationMessage) -> void: + message = notification_message + + +## Will be send as soon as the websocket connection is up and running you can use it to subscribe to events +signal session_id_received(id: String) + +## Will be called when an event is sent from Twitch. +signal event(type: StringName, data: Dictionary) + +## Will be called when an event is sent from Twitch. Same like event signal but better named and easier to use in inline awaits. +signal event_received(event: Event) + +## Will be called when an event got revoked from your subscription by Twitch. +signal events_revoked(type: StringName, status: String) + +## Called when any eventsub message is received for low level access +signal message_received(message: Variant) + + +@export var api: TwitchAPI +@export var _subscriptions: Array[TwitchEventsubConfig] = [] +@export var scopes: OAuthScopes +@export var eventsub_live_server_url: String = "wss://eventsub.wss.twitch.tv/ws" +@export var eventsub_test_server_url: String = "ws://127.0.0.1:8080/ws" +@export var use_test_server: bool +@export var ignore_message_eventsub_in_seconds: int = 600 + +var _client: WebsocketClient = WebsocketClient.new() +var _test_client : WebsocketClient = WebsocketClient.new() +## Swap over client in case Twitch sends us the message for a new server. +## See: https://dev.twitch.tv/docs/eventsub/handling-websocket-events/#reconnect-message +var _swap_over_client : WebsocketClient + +var session: Session +## Holds the messages that was processed already. +## Key: MessageID Value: Timestamp +var eventsub_messages: Dictionary = {} +var last_keepalive: int +var is_open: bool: + get(): return _client.is_open +var _should_connect: bool + +## When the Websocket server is shutting down and the client is doing a +## gracefull handover +var _swap_over_process: bool + +## queues the actions that should be executed when the connection is established +var _action_stack: Array[SubscriptionAction] +var _executing_action_stack: bool +## Increased on every reconnect without subscriptions +var _empty_connections: int + +## Determines the action that the subscription should do +class SubscriptionAction extends RefCounted: + var subscribe: bool + var subscription: TwitchEventsubConfig + + func _to_string() -> String: + return "%s %s" % [("Subscribe to" if subscribe else "Unsubscribe from"), subscription.definition.get_readable_name()] + + +func _init() -> void: + _client.connection_url = eventsub_live_server_url + _client.message_received.connect(_data_received) + _client.connection_established.connect(_on_connection_established) + _client.connection_closed.connect(_on_connection_closed) + _test_client.connection_url = eventsub_test_server_url + _test_client.message_received.connect(_data_received) + + +func _ready() -> void: + _client.name = "Websocket Client" + add_child(_client) + if use_test_server: + _test_client.name = "Websocket Client Test" + add_child(_test_client) + if api == null: api = TwitchAPI.instance + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +## Propergated call from twitch service +func do_setup() -> void: + await open_connection() + _log.i("Eventsub setup") + + +## Propergated call from twitch service +func do_unsetup() -> void: + for subscription in _subscriptions: + unsubscribe(subscription) + close_connection() + _log.i("Eventsub unsetup") + + +func wait_setup() -> void: + await wait_for_session_established() + + +## Waits until the eventsub is fully established +func wait_for_session_established() -> void: + if session == null: await session_id_received + + +func _on_connection_established() -> void: + if not _swap_over_process: + _action_stack.clear() + if _subscriptions.is_empty(): _empty_connections += 1 + if _empty_connections >= 3: + _empty_connections = 0 + _log.e("Stopped eventsub cause of no subscription.") + close_connection() + return + + # Resubscribe + _log.i("Connection established -> resubscribe to: [%s]" % [_subscriptions]) + for sub in _subscriptions: _add_action(sub, true) + _execute_action_stack() + + +func _on_connection_closed() -> void: + session = null + + +func open_connection() -> void: + if _client.is_closed: + _client.open_connection() + if _test_client.is_closed && use_test_server: + _test_client.open_connection() + + +func close_connection() -> void: + if not _client.is_closed: + _client.close() + if not _test_client.is_closed: + _test_client.close() + + +## Add a new subscription +func subscribe(eventsub_config: TwitchEventsubConfig) -> void: + _log.i("Subscribe to %s" % eventsub_config.definition.get_readable_name()) + _subscriptions.append(eventsub_config) + _add_action(eventsub_config, true) + _empty_connections = 0 + + +## Returns a list of subscriptions of the given type or empty if none. +func get_subscription_by_type(type: TwitchEventsubDefinition.Type) -> Array[TwitchEventsubConfig]: + var result: Array[TwitchEventsubConfig] = [] + for subscription in _subscriptions: + if subscription.type == type: + result.append(subscription) + return result + + +func has_subscription(eventsub_definition: TwitchEventsubDefinition, condition: Dictionary) -> bool: + for subscription: TwitchEventsubConfig in _subscriptions: + if subscription.definition == eventsub_definition && subscription.condition == condition: + return true + return false + + +## Remove a subscription +func unsubscribe(eventsub_config: TwitchEventsubConfig) -> void: + _subscriptions.erase(eventsub_config) + _add_action(eventsub_config, false) + + +## Process the queue of actions until its empty +func _execute_action_stack() -> void: + if _executing_action_stack: return + await wait_for_session_established() + _log.d("Execute actions [%s]" % [_action_stack]) + _executing_action_stack = true + while not _action_stack.is_empty(): + var action = _action_stack.pop_back() + var sub: TwitchEventsubConfig = action.subscription + if action.subscribe: + _subscribe(sub) + else: + _unsubscribe(sub) + _executing_action_stack = false + + +## Adds a subscribe or unsubscribe action to the queue +func _add_action(sub: TwitchEventsubConfig, subscribe: bool) -> void: + var sub_action = SubscriptionAction.new() + sub_action.subscription = sub + sub_action.subscribe = subscribe + _action_stack.append(sub_action) + _log.d("Add subscribe action: %s" % sub.definition.get_readable_name()) + _execute_action_stack() + + +## Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ +## for details on which API versions are available and which conditions are required. +func _subscribe(subscription: TwitchEventsubConfig) -> String: + var event_name = subscription.definition.value + var version = subscription.definition.version + var conditions = subscription.condition + + var data : TwitchCreateEventSubSubscription.Body = TwitchCreateEventSubSubscription.Body.new() + var transport : TwitchCreateEventSubSubscription.BodyTransport = TwitchCreateEventSubSubscription.BodyTransport.new() + data.type = event_name + data.version = version + data.condition = conditions + data.transport = transport + transport.method = "websocket" + transport.session_id = session.id + + _log.d("Do subscribe: %s" % event_name) + + var eventsub_response = await api.create_eventsub_subscription(data) + + if eventsub_response.response.response_code == 401: + _log.e("Subscription failed for '%s': Missing authentication for eventsub. The token got not authenticated yet. Please login!" % data.type) + _client.close(3000, "Missing Authentication") + return "" + elif eventsub_response.response.response_code == 403: + _log.e("Subscription failed for '%s': The token is missing proper scopes. [url='%s']Please check documentation[/url]!" % [data.type, subscription.definition.documentation_link]) + _log.d(eventsub_response.response.response_data.get_string_from_utf8()) + _client.close(3003, "Missing Authorization") + return "" + if eventsub_response.response.response_code < 200 || eventsub_response.response.response_code >= 300: + _log.e("Subscription failed for '%s'. Unknown error %s: %s" % [data.type, eventsub_response.response.response_code, eventsub_response.response.response_data.get_string_from_utf8()]) + return "" + elif (eventsub_response.response.response_data.is_empty()): + return "" + _log.i("Now listening to '%s' events." % data.type) + + var result = JSON.parse_string(eventsub_response.response.response_data.get_string_from_utf8()) + var subscription_id = result.data[0].id + subscription.id = subscription_id + return subscription_id + + +## Unsubscribes from an eventsub in case of an error returns false +func _unsubscribe(subscription: TwitchEventsubConfig) -> bool: + var response = await api.delete_eventsub_subscription(subscription.id) + return response.error || response.response_code != 200 + + +func _data_received(data : PackedByteArray) -> void: + var message_str : String = data.get_string_from_utf8() + var message_json : Dictionary = JSON.parse_string(message_str) + if not message_json.has("metadata"): + _log.e("Twitch send something undocumented: %s" % message_str) + return + var metadata : Metadata = Metadata.new(message_json["metadata"]) + var id = metadata.message_id + var timestamp_str = metadata.message_timestamp + var timestamp = Time.get_unix_time_from_datetime_string(timestamp_str) + + if(_message_got_processed(id) || _message_is_to_old(timestamp)): + return + + eventsub_messages[id] = timestamp + last_keepalive = Time.get_ticks_msec() + + match metadata.message_type: + "session_welcome": + var welcome_message = TwitchWelcomeMessage.new(message_json) + session = welcome_message.payload.session + session_id_received.emit(session.id) + _log.i("Session established %s" % session.id) + message_received.emit(welcome_message) + "session_keepalive": + # Notification from server that the connection is still alive + var keep_alive_message = TwitchKeepaliveMessage.new(message_json) + message_received.emit(keep_alive_message) + pass + "session_reconnect": + var reconnect_message = TwitchReconnectMessage.new(message_json) + message_received.emit(reconnect_message) + _handle_reconnect(reconnect_message) + "revocation": + var revocation_message = TwitchRevocationMessage.new(message_json) + message_received.emit(revocation_message) + events_revoked.emit(revocation_message.payload.subscription.type, + revocation_message.payload.subscription.status) + "notification": + var notification_message = TwitchNotificationMessage.new(message_json) + message_received.emit(notification_message) + event.emit(notification_message.payload.subscription.type, + notification_message.payload.event) + event_received.emit(Event.new(notification_message)) + _cleanup() + + +func _handle_reconnect(reconnect_message: TwitchReconnectMessage): + _log.i("Session is forced to reconnect") + _swap_over_process = true + var reconnect_url = reconnect_message.payload.session.reconnect_url + _swap_over_client = WebsocketClient.new() + _swap_over_client.message_received.connect(_data_received) + _swap_over_client.connection_established.connect(_on_connection_established) + _swap_over_client.connection_url = reconnect_url + add_child(_swap_over_client) + _swap_over_client.open_connection() + await session_id_received + _client.close(1000, "Closed cause of reconnect.") + remove_child(_client) + _client = _swap_over_client + _swap_over_client = null + _swap_over_process = false + _log.i("Session reconnected on %s" % reconnect_url) + + +## Cleanup old messages that won't be processed anymore cause of time to prevent a +## memory problem on long runinng applications. +func _cleanup() -> void: + for message_id in eventsub_messages.keys(): + var timestamp = eventsub_messages[message_id] + if _message_is_to_old(timestamp): + eventsub_messages.erase(message_id) + + +func _message_got_processed(message_id: String) -> bool: + return eventsub_messages.has(message_id) + + +func _message_is_to_old(timestamp: int) -> bool: + return timestamp < Time.get_unix_time_from_system() - ignore_message_eventsub_in_seconds + + +func get_client() -> WebsocketClient: + return _client + + +func get_test_client() -> WebsocketClient: + return _test_client + +## Returns a copy of the current subscribed events. Don't modify the result they won't get applied anyway. +func get_subscriptions() -> Array[TwitchEventsubConfig]: + return _subscriptions.duplicate() diff --git a/addons/twitcher/eventsub/twitch_eventsub.gd.uid b/addons/twitcher/eventsub/twitch_eventsub.gd.uid new file mode 100644 index 00000000..90a3a5d2 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub.gd.uid @@ -0,0 +1 @@ +uid://blmhj3j00yk45 diff --git a/addons/twitcher/eventsub/twitch_eventsub_config.gd b/addons/twitcher/eventsub/twitch_eventsub_config.gd new file mode 100644 index 00000000..cd6bc7f9 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub_config.gd @@ -0,0 +1,47 @@ +@tool +extends Resource + +## Defines howto subscribe to a eventsub subscription. +class_name TwitchEventsubConfig +static var _log: TwitchLogger = TwitchLogger.new("TwitchEventsubConfig") + +## What do you want to subscribe +@export var type: TwitchEventsubDefinition.Type: + set = _update_type + +## How do you want to subscribe defined by `definition conditions`. +@export var condition: Dictionary = {} + +var definition: TwitchEventsubDefinition: + get(): return TwitchEventsubDefinition.ALL[type] + +## Send from the server to identify the subscription for unsubscribing +var id: String + +## Called when type changed +signal type_changed(new_type: TwitchEventsubDefinition.Type) + + +static func create(definition: TwitchEventsubDefinition, conditions: Dictionary) -> TwitchEventsubConfig: + var config = TwitchEventsubConfig.new() + config.type = definition.type + config.condition = conditions + for condition_name: StringName in definition.conditions: + if not conditions.has(condition_name): + _log.i("You miss probably following condition %s" % condition_name) + return config + + +func _update_type(val: TwitchEventsubDefinition.Type) -> void: + if type != val: + type = val + var definition: TwitchEventsubDefinition = TwitchEventsubDefinition.ALL[type] + var new_condition: Dictionary = {} + for condition_key: StringName in definition.conditions: + new_condition[condition_key] = condition.get(condition_key, "") + condition = new_condition + type_changed.emit(val) + + +func _to_string() -> String: + return "%s" % definition.get_readable_name() diff --git a/addons/twitcher/eventsub/twitch_eventsub_config.gd.uid b/addons/twitcher/eventsub/twitch_eventsub_config.gd.uid new file mode 100644 index 00000000..e669124e --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub_config.gd.uid @@ -0,0 +1 @@ +uid://cjug64e3433g0 diff --git a/addons/twitcher/eventsub/twitch_eventsub_definition.gd b/addons/twitcher/eventsub/twitch_eventsub_definition.gd new file mode 100644 index 00000000..f8a79262 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub_definition.gd @@ -0,0 +1,357 @@ +@tool +extends Object + +class_name TwitchEventsubDefinition + +## All supported subscriptions should be used in comination with get_all method as index. +enum Type { + 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_MODERATE_V2, + 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_UPDATE, + CHANNEL_SUSPICIOUS_USER_MESSAGE, + CHANNEL_VIP_ADD, + CHANNEL_VIP_REMOVE, + CHANNEL_WARNING_ACKNOWLEDGE, + CHANNEL_WARNING_SEND, + CHANNEL_HYPE_TRAIN_BEGIN, + CHANNEL_HYPE_TRAIN_PROGRESS, + CHANNEL_HYPE_TRAIN_END, + CHANNEL_CHARITY_CAMPAIGN_DONATE, + CHANNEL_CHARITY_CAMPAIGN_START, + CHANNEL_CHARITY_CAMPAIGN_PROGRESS, + CHANNEL_CHARITY_CAMPAIGN_STOP, + CHANNEL_SHARED_CHAT_BEGIN, + CHANNEL_SHARED_CHAT_UPDATE, + CHANNEL_SHARED_CHAT_END, + CHANNEL_SHIELD_MODE_BEGIN, + CHANNEL_SHIELD_MODE_END, + CHANNEL_SHOUTOUT_CREATE, + CHANNEL_SHOUTOUT_RECEIVE, + CONDUIT_SHARD_DISABLED, + DROP_ENTITLEMENT_GRANT, + EXTENSION_BITS_TRANSACTION_CREATE, + CHANNEL_GOAL_BEGIN, + CHANNEL_GOAL_PROGRESS, + CHANNEL_GOAL_END, + STREAM_ONLINE, + STREAM_OFFLINE, + USER_AUTHORIZATION_GRANT, + USER_AUTHORIZATION_REVOKE, + USER_UPDATE, + USER_WHISPER_MESSAGE, +} + +## The type of itself +var type: Type +## Name within Twitch +var value: StringName +## Version defined in Twitch +var version: StringName +## Keys of the conditions it need for setup +var conditions: Array[StringName] +## Possible scopes it needs (on some of them its more then needed) +var scopes: Array[StringName] +## Link to the twitch documentation +var documentation_link: String + + +func _init(typ: Type, val: StringName, ver: StringName, cond: Array[StringName], scps: Array[StringName], doc_link: String): + type = typ + value = val + version = ver + conditions = cond + scopes = scps + documentation_link = doc_link + +## Get a human readable name of it +func get_readable_name() -> String: + return "%s (v%s)" % [value, version] + + +static var AUTOMOD_MESSAGE_HOLD := TwitchEventsubDefinition.new(Type.AUTOMOD_MESSAGE_HOLD, &"automod.message.hold", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:automod"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodmessagehold") +static var AUTOMOD_MESSAGE_UPDATE := TwitchEventsubDefinition.new(Type.AUTOMOD_MESSAGE_UPDATE, &"automod.message.update", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:automod"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodmessageupdate") +static var AUTOMOD_SETTINGS_UPDATE := TwitchEventsubDefinition.new(Type.AUTOMOD_SETTINGS_UPDATE, &"automod.settings.update", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:automod_settings"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodsettingsupdate") +static var AUTOMOD_TERMS_UPDATE := TwitchEventsubDefinition.new(Type.AUTOMOD_TERMS_UPDATE, &"automod.terms.update", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:automod"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#automodtermsupdate") +static var CHANNEL_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_UPDATE, &"channel.update", &"2", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelupdate") +static var CHANNEL_FOLLOW := TwitchEventsubDefinition.new(Type.CHANNEL_FOLLOW, &"channel.follow", &"2", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:followers"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelfollow") +static var CHANNEL_AD_BREAK_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_AD_BREAK_BEGIN, &"channel.ad_break.begin", &"1", [&"broadcaster_user_id"], [&"channel:read:ads"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelad_breakbegin") +static var CHANNEL_CHAT_CLEAR := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_CLEAR, &"channel.chat.clear", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatclear") +static var CHANNEL_CHAT_CLEAR_USER_MESSAGES := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_CLEAR_USER_MESSAGES, &"channel.chat.clear_user_messages", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatclear_user_messages") +static var CHANNEL_CHAT_MESSAGE := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_MESSAGE, &"channel.chat.message", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatmessage") +static var CHANNEL_CHAT_MESSAGE_DELETE := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_MESSAGE_DELETE, &"channel.chat.message_delete", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatmessage_delete") +static var CHANNEL_CHAT_NOTIFICATION := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_NOTIFICATION, &"channel.chat.notification", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatnotification") +static var CHANNEL_CHAT_SETTINGS_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_SETTINGS_UPDATE, &"channel.chat_settings.update", &"1", [&"broadcaster_user_id",&"user_id"], [&"channel:bot",&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchat_settingsupdate") +static var CHANNEL_CHAT_USER_MESSAGE_HOLD := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_USER_MESSAGE_HOLD, &"channel.chat.user_message_hold", &"1", [&"broadcaster_user_id",&"user_id"], [&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatuser_message_hold") +static var CHANNEL_CHAT_USER_MESSAGE_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_CHAT_USER_MESSAGE_UPDATE, &"channel.chat.user_message_update", &"1", [&"broadcaster_user_id",&"user_id"], [&"user:bot",&"user:read:chat"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchatuser_message_update") +static var CHANNEL_SUBSCRIBE := TwitchEventsubDefinition.new(Type.CHANNEL_SUBSCRIBE, &"channel.subscribe", &"1", [&"broadcaster_user_id"], [&"channel:read:subscriptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsubscribe") +static var CHANNEL_SUBSCRIPTION_END := TwitchEventsubDefinition.new(Type.CHANNEL_SUBSCRIPTION_END, &"channel.subscription.end", &"1", [&"broadcaster_user_id"], [&"channel:read:subscriptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsubscriptionend") +static var CHANNEL_SUBSCRIPTION_GIFT := TwitchEventsubDefinition.new(Type.CHANNEL_SUBSCRIPTION_GIFT, &"channel.subscription.gift", &"1", [&"broadcaster_user_id"], [&"channel:read:subscriptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsubscriptiongift") +static var CHANNEL_SUBSCRIPTION_MESSAGE := TwitchEventsubDefinition.new(Type.CHANNEL_SUBSCRIPTION_MESSAGE, &"channel.subscription.message", &"1", [&"broadcaster_user_id"], [&"channel:read:subscriptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsubscriptionmessage") +static var CHANNEL_CHEER := TwitchEventsubDefinition.new(Type.CHANNEL_CHEER, &"channel.cheer", &"1", [&"broadcaster_user_id"], [&"bits:read"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelcheer") +static var CHANNEL_RAID := TwitchEventsubDefinition.new(Type.CHANNEL_RAID, &"channel.raid", &"1", [&"to_broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelraid") +static var CHANNEL_BAN := TwitchEventsubDefinition.new(Type.CHANNEL_BAN, &"channel.ban", &"1", [&"broadcaster_user_id"], [&"channel:moderate"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelban") +static var CHANNEL_UNBAN := TwitchEventsubDefinition.new(Type.CHANNEL_UNBAN, &"channel.unban", &"1", [&"broadcaster_user_id"], [&"channel:moderate"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelunban") +static var CHANNEL_UNBAN_REQUEST_CREATE := TwitchEventsubDefinition.new(Type.CHANNEL_UNBAN_REQUEST_CREATE, &"channel.unban_request.create", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:unban_requests",&"moderator:manage:unban_requests"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelunban_requestcreate") +static var CHANNEL_UNBAN_REQUEST_RESOLVE := TwitchEventsubDefinition.new(Type.CHANNEL_UNBAN_REQUEST_RESOLVE, &"channel.unban_request.resolve", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:unban_requests",&"moderator:manage:unban_requests"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelunban_requestresolve") +static var CHANNEL_MODERATE := TwitchEventsubDefinition.new(Type.CHANNEL_MODERATE, &"channel.moderate", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:banned_users",&"moderator:manage:blocked_terms",&"moderator:read:banned_users",&"moderator:manage:chat_messages",&"moderator:manage:unban_requests",&"moderator:manage:chat_settings",&"moderator:read:unban_requests",&"moderator:read:chat_settings",&"moderator:read:vips",&"moderator:read:chat_messages",&"moderator:read:blocked_terms",&"moderator:read:moderators"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelmoderate") +static var CHANNEL_MODERATE_V2 := TwitchEventsubDefinition.new(Type.CHANNEL_MODERATE_V2, &"channel.moderate", &"2", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:banned_users",&"moderator:manage:blocked_terms",&"moderator:read:banned_users",&"moderator:manage:chat_messages",&"moderator:manage:unban_requests",&"moderator:manage:warnings",&"moderator:manage:chat_settings",&"moderator:read:unban_requests",&"moderator:read:chat_settings",&"moderator:read:vips",&"moderator:read:warnings",&"moderator:read:chat_messages",&"moderator:read:blocked_terms",&"moderator:read:moderators"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelmoderate-v2") +static var CHANNEL_MODERATOR_ADD := TwitchEventsubDefinition.new(Type.CHANNEL_MODERATOR_ADD, &"channel.moderator.add", &"1", [&"broadcaster_user_id"], [&"moderation:read"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelmoderatoradd") +static var CHANNEL_MODERATOR_REMOVE := TwitchEventsubDefinition.new(Type.CHANNEL_MODERATOR_REMOVE, &"channel.moderator.remove", &"1", [&"broadcaster_user_id"], [&"moderation:read"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelmoderatorremove") +static var CHANNEL_GUEST_STAR_SESSION_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_GUEST_STAR_SESSION_BEGIN, &"channel.guest_star_session.begin", &"beta", [&"broadcaster_user_id",&"moderator_user_id"], [&"channel:read:guest_star",&"moderator:manage:guest_star",&"moderator:read:guest_star",&"channel:manage:guest_star"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelguest_star_sessionbegin") +static var CHANNEL_GUEST_STAR_SESSION_END := TwitchEventsubDefinition.new(Type.CHANNEL_GUEST_STAR_SESSION_END, &"channel.guest_star_session.end", &"beta", [&"broadcaster_user_id",&"moderator_user_id"], [&"channel:read:guest_star",&"moderator:manage:guest_star",&"moderator:read:guest_star",&"channel:manage:guest_star"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelguest_star_sessionend") +static var CHANNEL_GUEST_STAR_GUEST_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_GUEST_STAR_GUEST_UPDATE, &"channel.guest_star_guest.update", &"beta", [&"broadcaster_user_id",&"moderator_user_id"], [&"channel:read:guest_star",&"moderator:manage:guest_star",&"moderator:read:guest_star",&"channel:manage:guest_star"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelguest_star_guestupdate") +static var CHANNEL_GUEST_STAR_SETTINGS_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_GUEST_STAR_SETTINGS_UPDATE, &"channel.guest_star_settings.update", &"beta", [&"broadcaster_user_id",&"moderator_user_id"], [&"channel:read:guest_star",&"moderator:manage:guest_star",&"moderator:read:guest_star",&"channel:manage:guest_star"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelguest_star_settingsupdate") +static var CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, &"channel.channel_points_automatic_reward_redemption.add", &"1", [&"broadcaster_user_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_automatic_reward_redemptionadd") +static var CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, &"channel.channel_points_custom_reward.add", &"1", [&"broadcaster_user_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_custom_rewardadd") +static var CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, &"channel.channel_points_custom_reward.update", &"1", [&"broadcaster_user_id",&"reward_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_custom_rewardupdate") +static var CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, &"channel.channel_points_custom_reward.remove", &"1", [&"broadcaster_user_id",&"reward_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_custom_rewardremove") +static var CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, &"channel.channel_points_custom_reward_redemption.add", &"1", [&"broadcaster_user_id",&"reward_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_custom_reward_redemptionadd") +static var CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, &"channel.channel_points_custom_reward_redemption.update", &"1", [&"broadcaster_user_id",&"reward_id"], [&"channel:read:redemptions",&"channel:manage:redemptions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelchannel_points_custom_reward_redemptionupdate") +static var CHANNEL_POLL_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_POLL_BEGIN, &"channel.poll.begin", &"1", [&"broadcaster_user_id"], [&"channel:manage:polls",&"channel:read:polls"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpollbegin") +static var CHANNEL_POLL_PROGRESS := TwitchEventsubDefinition.new(Type.CHANNEL_POLL_PROGRESS, &"channel.poll.progress", &"1", [&"broadcaster_user_id"], [&"channel:manage:polls",&"channel:read:polls"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpollprogress") +static var CHANNEL_POLL_END := TwitchEventsubDefinition.new(Type.CHANNEL_POLL_END, &"channel.poll.end", &"1", [&"broadcaster_user_id"], [&"channel:manage:polls",&"channel:read:polls"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpollend") +static var CHANNEL_PREDICTION_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_PREDICTION_BEGIN, &"channel.prediction.begin", &"1", [&"broadcaster_user_id"], [&"channel:manage:predictions",&"channel:read:predictions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpredictionbegin") +static var CHANNEL_PREDICTION_PROGRESS := TwitchEventsubDefinition.new(Type.CHANNEL_PREDICTION_PROGRESS, &"channel.prediction.progress", &"1", [&"broadcaster_user_id"], [&"channel:manage:predictions",&"channel:read:predictions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpredictionprogress") +static var CHANNEL_PREDICTION_LOCK := TwitchEventsubDefinition.new(Type.CHANNEL_PREDICTION_LOCK, &"channel.prediction.lock", &"1", [&"broadcaster_user_id"], [&"channel:manage:predictions",&"channel:read:predictions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpredictionlock") +static var CHANNEL_PREDICTION_END := TwitchEventsubDefinition.new(Type.CHANNEL_PREDICTION_END, &"channel.prediction.end", &"1", [&"broadcaster_user_id"], [&"channel:manage:predictions",&"channel:read:predictions"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelpredictionend") +static var CHANNEL_SUSPICIOUS_USER_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_SUSPICIOUS_USER_UPDATE, &"channel.suspicious_user.update", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:suspicious_users"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsuspicious_userupdate") +static var CHANNEL_SUSPICIOUS_USER_MESSAGE := TwitchEventsubDefinition.new(Type.CHANNEL_SUSPICIOUS_USER_MESSAGE, &"channel.suspicious_user.message", &"1", [&"moderator_user_id",&"broadcaster_user_id"], [&"moderator:read:suspicious_users"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelsuspicious_usermessage") +static var CHANNEL_VIP_ADD := TwitchEventsubDefinition.new(Type.CHANNEL_VIP_ADD, &"channel.vip.add", &"1", [&"broadcaster_user_id"], [&"channel:manage:vips",&"channel:read:vips"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelvipadd") +static var CHANNEL_VIP_REMOVE := TwitchEventsubDefinition.new(Type.CHANNEL_VIP_REMOVE, &"channel.vip.remove", &"1", [&"broadcaster_user_id"], [&"channel:manage:vips",&"channel:read:vips"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelvipremove") +static var CHANNEL_WARNING_ACKNOWLEDGE := TwitchEventsubDefinition.new(Type.CHANNEL_WARNING_ACKNOWLEDGE, &"channel.warning.acknowledge", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:warnings",&"moderator:read:warnings"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelwarningacknowledge") +static var CHANNEL_WARNING_SEND := TwitchEventsubDefinition.new(Type.CHANNEL_WARNING_SEND, &"channel.warning.send", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:manage:warnings",&"moderator:read:warnings"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelwarningsend") +static var CHANNEL_HYPE_TRAIN_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_HYPE_TRAIN_BEGIN, &"channel.hype_train.begin", &"1", [&"broadcaster_user_id"], [&"channel:read:hype_train"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelhype_trainbegin") +static var CHANNEL_HYPE_TRAIN_PROGRESS := TwitchEventsubDefinition.new(Type.CHANNEL_HYPE_TRAIN_PROGRESS, &"channel.hype_train.progress", &"1", [&"broadcaster_user_id"], [&"channel:read:hype_train"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelhype_trainprogress") +static var CHANNEL_HYPE_TRAIN_END := TwitchEventsubDefinition.new(Type.CHANNEL_HYPE_TRAIN_END, &"channel.hype_train.end", &"1", [&"broadcaster_user_id"], [&"channel:read:hype_train"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelhype_trainend") +static var CHANNEL_CHARITY_CAMPAIGN_DONATE := TwitchEventsubDefinition.new(Type.CHANNEL_CHARITY_CAMPAIGN_DONATE, &"channel.charity_campaign.donate", &"1", [&"broadcaster_user_id"], [&"channel:read:charity"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelcharity_campaigndonate") +static var CHANNEL_CHARITY_CAMPAIGN_START := TwitchEventsubDefinition.new(Type.CHANNEL_CHARITY_CAMPAIGN_START, &"channel.charity_campaign.start", &"1", [&"broadcaster_user_id"], [&"channel:read:charity"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelcharity_campaignstart") +static var CHANNEL_CHARITY_CAMPAIGN_PROGRESS := TwitchEventsubDefinition.new(Type.CHANNEL_CHARITY_CAMPAIGN_PROGRESS, &"channel.charity_campaign.progress", &"1", [&"broadcaster_user_id"], [&"channel:read:charity"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelcharity_campaignprogress") +static var CHANNEL_CHARITY_CAMPAIGN_STOP := TwitchEventsubDefinition.new(Type.CHANNEL_CHARITY_CAMPAIGN_STOP, &"channel.charity_campaign.stop", &"1", [&"broadcaster_user_id"], [&"channel:read:charity"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelcharity_campaignstop") +static var CHANNEL_SHARED_CHAT_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_SHARED_CHAT_BEGIN, &"channel.shared_chat.begin", &"beta", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshared_chatbegin") +static var CHANNEL_SHARED_CHAT_UPDATE := TwitchEventsubDefinition.new(Type.CHANNEL_SHARED_CHAT_UPDATE, &"channel.shared_chat.update", &"beta", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshared_chatupdate") +static var CHANNEL_SHARED_CHAT_END := TwitchEventsubDefinition.new(Type.CHANNEL_SHARED_CHAT_END, &"channel.shared_chat.end", &"beta", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshared_chatend") +static var CHANNEL_SHIELD_MODE_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_SHIELD_MODE_BEGIN, &"channel.shield_mode.begin", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:shield_mode",&"moderator:manage:shield_mode"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshield_modebegin") +static var CHANNEL_SHIELD_MODE_END := TwitchEventsubDefinition.new(Type.CHANNEL_SHIELD_MODE_END, &"channel.shield_mode.end", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:shield_mode",&"moderator:manage:shield_mode"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshield_modeend") +static var CHANNEL_SHOUTOUT_CREATE := TwitchEventsubDefinition.new(Type.CHANNEL_SHOUTOUT_CREATE, &"channel.shoutout.create", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:shoutouts",&"moderator:manage:shoutouts"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshoutoutcreate") +static var CHANNEL_SHOUTOUT_RECEIVE := TwitchEventsubDefinition.new(Type.CHANNEL_SHOUTOUT_RECEIVE, &"channel.shoutout.receive", &"1", [&"broadcaster_user_id",&"moderator_user_id"], [&"moderator:read:shoutouts",&"moderator:manage:shoutouts"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelshoutoutreceive") +static var CONDUIT_SHARD_DISABLED := TwitchEventsubDefinition.new(Type.CONDUIT_SHARD_DISABLED, &"conduit.shard.disabled", &"1", [&"client_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#conduitsharddisabled") +static var DROP_ENTITLEMENT_GRANT := TwitchEventsubDefinition.new(Type.DROP_ENTITLEMENT_GRANT, &"drop.entitlement.grant", &"1", [&"organization_id",&"category_id",&"campaign_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#dropentitlementgrant") +static var EXTENSION_BITS_TRANSACTION_CREATE := TwitchEventsubDefinition.new(Type.EXTENSION_BITS_TRANSACTION_CREATE, &"extension.bits_transaction.create", &"1", [&"extension_client_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#extensionbits_transactioncreate") +static var CHANNEL_GOAL_BEGIN := TwitchEventsubDefinition.new(Type.CHANNEL_GOAL_BEGIN, &"channel.goal.begin", &"1", [&"broadcaster_user_id"], [&"channel:read:goals"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelgoalbegin") +static var CHANNEL_GOAL_PROGRESS := TwitchEventsubDefinition.new(Type.CHANNEL_GOAL_PROGRESS, &"channel.goal.progress", &"1", [&"broadcaster_user_id"], [&"channel:read:goals"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelgoalprogress") +static var CHANNEL_GOAL_END := TwitchEventsubDefinition.new(Type.CHANNEL_GOAL_END, &"channel.goal.end", &"1", [&"broadcaster_user_id"], [&"channel:read:goals"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#channelgoalend") +static var STREAM_ONLINE := TwitchEventsubDefinition.new(Type.STREAM_ONLINE, &"stream.online", &"1", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#streamonline") +static var STREAM_OFFLINE := TwitchEventsubDefinition.new(Type.STREAM_OFFLINE, &"stream.offline", &"1", [&"broadcaster_user_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#streamoffline") +static var USER_AUTHORIZATION_GRANT := TwitchEventsubDefinition.new(Type.USER_AUTHORIZATION_GRANT, &"user.authorization.grant", &"1", [&"client_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#userauthorizationgrant") +static var USER_AUTHORIZATION_REVOKE := TwitchEventsubDefinition.new(Type.USER_AUTHORIZATION_REVOKE, &"user.authorization.revoke", &"1", [&"client_id"], [], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#userauthorizationrevoke") +static var USER_UPDATE := TwitchEventsubDefinition.new(Type.USER_UPDATE, &"user.update", &"1", [&"user_id"], [&"user:read:email"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#userupdate") +static var USER_WHISPER_MESSAGE := TwitchEventsubDefinition.new(Type.USER_WHISPER_MESSAGE, &"user.whisper.message", &"1", [&"user_id"], [&"user:manage:whispers",&"user:read:whispers"], "https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#userwhispermessage") + + +## Returns all supported subscriptions +static var ALL: Dictionary[TwitchEventsubDefinition.Type, TwitchEventsubDefinition] = { + Type.AUTOMOD_MESSAGE_HOLD: AUTOMOD_MESSAGE_HOLD, + Type.AUTOMOD_MESSAGE_UPDATE: AUTOMOD_MESSAGE_UPDATE, + Type.AUTOMOD_SETTINGS_UPDATE: AUTOMOD_SETTINGS_UPDATE, + Type.AUTOMOD_TERMS_UPDATE: AUTOMOD_TERMS_UPDATE, + Type.CHANNEL_UPDATE: CHANNEL_UPDATE, + Type.CHANNEL_FOLLOW: CHANNEL_FOLLOW, + Type.CHANNEL_AD_BREAK_BEGIN: CHANNEL_AD_BREAK_BEGIN, + Type.CHANNEL_CHAT_CLEAR: CHANNEL_CHAT_CLEAR, + Type.CHANNEL_CHAT_CLEAR_USER_MESSAGES: CHANNEL_CHAT_CLEAR_USER_MESSAGES, + Type.CHANNEL_CHAT_MESSAGE: CHANNEL_CHAT_MESSAGE, + Type.CHANNEL_CHAT_MESSAGE_DELETE: CHANNEL_CHAT_MESSAGE_DELETE, + Type.CHANNEL_CHAT_NOTIFICATION: CHANNEL_CHAT_NOTIFICATION, + Type.CHANNEL_CHAT_SETTINGS_UPDATE: CHANNEL_CHAT_SETTINGS_UPDATE, + Type.CHANNEL_CHAT_USER_MESSAGE_HOLD: CHANNEL_CHAT_USER_MESSAGE_HOLD, + Type.CHANNEL_CHAT_USER_MESSAGE_UPDATE: CHANNEL_CHAT_USER_MESSAGE_UPDATE, + Type.CHANNEL_SUBSCRIBE: CHANNEL_SUBSCRIBE, + Type.CHANNEL_SUBSCRIPTION_END: CHANNEL_SUBSCRIPTION_END, + Type.CHANNEL_SUBSCRIPTION_GIFT: CHANNEL_SUBSCRIPTION_GIFT, + Type.CHANNEL_SUBSCRIPTION_MESSAGE: CHANNEL_SUBSCRIPTION_MESSAGE, + Type.CHANNEL_CHEER: CHANNEL_CHEER, + Type.CHANNEL_RAID: CHANNEL_RAID, + Type.CHANNEL_BAN: CHANNEL_BAN, + Type.CHANNEL_UNBAN: CHANNEL_UNBAN, + Type.CHANNEL_UNBAN_REQUEST_CREATE: CHANNEL_UNBAN_REQUEST_CREATE, + Type.CHANNEL_UNBAN_REQUEST_RESOLVE: CHANNEL_UNBAN_REQUEST_RESOLVE, + Type.CHANNEL_MODERATE: CHANNEL_MODERATE, + Type.CHANNEL_MODERATE_V2: CHANNEL_MODERATE_V2, + Type.CHANNEL_MODERATOR_ADD: CHANNEL_MODERATOR_ADD, + Type.CHANNEL_MODERATOR_REMOVE: CHANNEL_MODERATOR_REMOVE, + Type.CHANNEL_GUEST_STAR_SESSION_BEGIN: CHANNEL_GUEST_STAR_SESSION_BEGIN, + Type.CHANNEL_GUEST_STAR_SESSION_END: CHANNEL_GUEST_STAR_SESSION_END, + Type.CHANNEL_GUEST_STAR_GUEST_UPDATE: CHANNEL_GUEST_STAR_GUEST_UPDATE, + Type.CHANNEL_GUEST_STAR_SETTINGS_UPDATE: CHANNEL_GUEST_STAR_SETTINGS_UPDATE, + Type.CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD: CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, + Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, + Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, + Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, + Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, + Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, + Type.CHANNEL_POLL_BEGIN: CHANNEL_POLL_BEGIN, + Type.CHANNEL_POLL_PROGRESS: CHANNEL_POLL_PROGRESS, + Type.CHANNEL_POLL_END: CHANNEL_POLL_END, + Type.CHANNEL_PREDICTION_BEGIN: CHANNEL_PREDICTION_BEGIN, + Type.CHANNEL_PREDICTION_PROGRESS: CHANNEL_PREDICTION_PROGRESS, + Type.CHANNEL_PREDICTION_LOCK: CHANNEL_PREDICTION_LOCK, + Type.CHANNEL_PREDICTION_END: CHANNEL_PREDICTION_END, + Type.CHANNEL_SUSPICIOUS_USER_UPDATE: CHANNEL_SUSPICIOUS_USER_UPDATE, + Type.CHANNEL_SUSPICIOUS_USER_MESSAGE: CHANNEL_SUSPICIOUS_USER_MESSAGE, + Type.CHANNEL_VIP_ADD: CHANNEL_VIP_ADD, + Type.CHANNEL_VIP_REMOVE: CHANNEL_VIP_REMOVE, + Type.CHANNEL_WARNING_ACKNOWLEDGE: CHANNEL_WARNING_ACKNOWLEDGE, + Type.CHANNEL_WARNING_SEND: CHANNEL_WARNING_SEND, + Type.CHANNEL_HYPE_TRAIN_BEGIN: CHANNEL_HYPE_TRAIN_BEGIN, + Type.CHANNEL_HYPE_TRAIN_PROGRESS: CHANNEL_HYPE_TRAIN_PROGRESS, + Type.CHANNEL_HYPE_TRAIN_END: CHANNEL_HYPE_TRAIN_END, + Type.CHANNEL_CHARITY_CAMPAIGN_DONATE: CHANNEL_CHARITY_CAMPAIGN_DONATE, + Type.CHANNEL_CHARITY_CAMPAIGN_START: CHANNEL_CHARITY_CAMPAIGN_START, + Type.CHANNEL_CHARITY_CAMPAIGN_PROGRESS: CHANNEL_CHARITY_CAMPAIGN_PROGRESS, + Type.CHANNEL_CHARITY_CAMPAIGN_STOP: CHANNEL_CHARITY_CAMPAIGN_STOP, + Type.CHANNEL_SHARED_CHAT_BEGIN: CHANNEL_SHARED_CHAT_BEGIN, + Type.CHANNEL_SHARED_CHAT_UPDATE: CHANNEL_SHARED_CHAT_UPDATE, + Type.CHANNEL_SHARED_CHAT_END: CHANNEL_SHARED_CHAT_END, + Type.CHANNEL_SHIELD_MODE_BEGIN: CHANNEL_SHIELD_MODE_BEGIN, + Type.CHANNEL_SHIELD_MODE_END: CHANNEL_SHIELD_MODE_END, + Type.CHANNEL_SHOUTOUT_CREATE: CHANNEL_SHOUTOUT_CREATE, + Type.CHANNEL_SHOUTOUT_RECEIVE: CHANNEL_SHOUTOUT_RECEIVE, + Type.CONDUIT_SHARD_DISABLED: CONDUIT_SHARD_DISABLED, + Type.DROP_ENTITLEMENT_GRANT: DROP_ENTITLEMENT_GRANT, + Type.EXTENSION_BITS_TRANSACTION_CREATE: EXTENSION_BITS_TRANSACTION_CREATE, + Type.CHANNEL_GOAL_BEGIN: CHANNEL_GOAL_BEGIN, + Type.CHANNEL_GOAL_PROGRESS: CHANNEL_GOAL_PROGRESS, + Type.CHANNEL_GOAL_END: CHANNEL_GOAL_END, + Type.STREAM_ONLINE: STREAM_ONLINE, + Type.STREAM_OFFLINE: STREAM_OFFLINE, + Type.USER_AUTHORIZATION_GRANT: USER_AUTHORIZATION_GRANT, + Type.USER_AUTHORIZATION_REVOKE: USER_AUTHORIZATION_REVOKE, + Type.USER_UPDATE: USER_UPDATE, + Type.USER_WHISPER_MESSAGE: USER_WHISPER_MESSAGE, +} + +## Returns all supported subscriptions by name +static var BY_NAME: Dictionary[StringName, TwitchEventsubDefinition] = { + AUTOMOD_MESSAGE_HOLD.value: AUTOMOD_MESSAGE_HOLD, + AUTOMOD_MESSAGE_UPDATE.value: AUTOMOD_MESSAGE_UPDATE, + AUTOMOD_SETTINGS_UPDATE.value: AUTOMOD_SETTINGS_UPDATE, + AUTOMOD_TERMS_UPDATE.value: AUTOMOD_TERMS_UPDATE, + CHANNEL_UPDATE.value: CHANNEL_UPDATE, + CHANNEL_FOLLOW.value: CHANNEL_FOLLOW, + CHANNEL_AD_BREAK_BEGIN.value: CHANNEL_AD_BREAK_BEGIN, + CHANNEL_CHAT_CLEAR.value: CHANNEL_CHAT_CLEAR, + CHANNEL_CHAT_CLEAR_USER_MESSAGES.value: CHANNEL_CHAT_CLEAR_USER_MESSAGES, + CHANNEL_CHAT_MESSAGE.value: CHANNEL_CHAT_MESSAGE, + CHANNEL_CHAT_MESSAGE_DELETE.value: CHANNEL_CHAT_MESSAGE_DELETE, + CHANNEL_CHAT_NOTIFICATION.value: CHANNEL_CHAT_NOTIFICATION, + CHANNEL_CHAT_SETTINGS_UPDATE.value: CHANNEL_CHAT_SETTINGS_UPDATE, + CHANNEL_CHAT_USER_MESSAGE_HOLD.value: CHANNEL_CHAT_USER_MESSAGE_HOLD, + CHANNEL_CHAT_USER_MESSAGE_UPDATE.value: CHANNEL_CHAT_USER_MESSAGE_UPDATE, + CHANNEL_SUBSCRIBE.value: CHANNEL_SUBSCRIBE, + CHANNEL_SUBSCRIPTION_END.value: CHANNEL_SUBSCRIPTION_END, + CHANNEL_SUBSCRIPTION_GIFT.value: CHANNEL_SUBSCRIPTION_GIFT, + CHANNEL_SUBSCRIPTION_MESSAGE.value: CHANNEL_SUBSCRIPTION_MESSAGE, + CHANNEL_CHEER.value: CHANNEL_CHEER, + CHANNEL_RAID.value: CHANNEL_RAID, + CHANNEL_BAN.value: CHANNEL_BAN, + CHANNEL_UNBAN.value: CHANNEL_UNBAN, + CHANNEL_UNBAN_REQUEST_CREATE.value: CHANNEL_UNBAN_REQUEST_CREATE, + CHANNEL_UNBAN_REQUEST_RESOLVE.value: CHANNEL_UNBAN_REQUEST_RESOLVE, + CHANNEL_MODERATE.value: CHANNEL_MODERATE, + CHANNEL_MODERATE_V2.value: CHANNEL_MODERATE_V2, + CHANNEL_MODERATOR_ADD.value: CHANNEL_MODERATOR_ADD, + CHANNEL_MODERATOR_REMOVE.value: CHANNEL_MODERATOR_REMOVE, + CHANNEL_GUEST_STAR_SESSION_BEGIN.value: CHANNEL_GUEST_STAR_SESSION_BEGIN, + CHANNEL_GUEST_STAR_SESSION_END.value: CHANNEL_GUEST_STAR_SESSION_END, + CHANNEL_GUEST_STAR_GUEST_UPDATE.value: CHANNEL_GUEST_STAR_GUEST_UPDATE, + CHANNEL_GUEST_STAR_SETTINGS_UPDATE.value: CHANNEL_GUEST_STAR_SETTINGS_UPDATE, + CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD.value: CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD.value: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE.value: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE.value: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD.value: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE.value: CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, + CHANNEL_POLL_BEGIN.value: CHANNEL_POLL_BEGIN, + CHANNEL_POLL_PROGRESS.value: CHANNEL_POLL_PROGRESS, + CHANNEL_POLL_END.value: CHANNEL_POLL_END, + CHANNEL_PREDICTION_BEGIN.value: CHANNEL_PREDICTION_BEGIN, + CHANNEL_PREDICTION_PROGRESS.value: CHANNEL_PREDICTION_PROGRESS, + CHANNEL_PREDICTION_LOCK.value: CHANNEL_PREDICTION_LOCK, + CHANNEL_PREDICTION_END.value: CHANNEL_PREDICTION_END, + CHANNEL_SUSPICIOUS_USER_UPDATE.value: CHANNEL_SUSPICIOUS_USER_UPDATE, + CHANNEL_SUSPICIOUS_USER_MESSAGE.value: CHANNEL_SUSPICIOUS_USER_MESSAGE, + CHANNEL_VIP_ADD.value: CHANNEL_VIP_ADD, + CHANNEL_VIP_REMOVE.value: CHANNEL_VIP_REMOVE, + CHANNEL_WARNING_ACKNOWLEDGE.value: CHANNEL_WARNING_ACKNOWLEDGE, + CHANNEL_WARNING_SEND.value: CHANNEL_WARNING_SEND, + CHANNEL_HYPE_TRAIN_BEGIN.value: CHANNEL_HYPE_TRAIN_BEGIN, + CHANNEL_HYPE_TRAIN_PROGRESS.value: CHANNEL_HYPE_TRAIN_PROGRESS, + CHANNEL_HYPE_TRAIN_END.value: CHANNEL_HYPE_TRAIN_END, + CHANNEL_CHARITY_CAMPAIGN_DONATE.value: CHANNEL_CHARITY_CAMPAIGN_DONATE, + CHANNEL_CHARITY_CAMPAIGN_START.value: CHANNEL_CHARITY_CAMPAIGN_START, + CHANNEL_CHARITY_CAMPAIGN_PROGRESS.value: CHANNEL_CHARITY_CAMPAIGN_PROGRESS, + CHANNEL_CHARITY_CAMPAIGN_STOP.value: CHANNEL_CHARITY_CAMPAIGN_STOP, + CHANNEL_SHARED_CHAT_BEGIN.value: CHANNEL_SHARED_CHAT_BEGIN, + CHANNEL_SHARED_CHAT_UPDATE.value: CHANNEL_SHARED_CHAT_UPDATE, + CHANNEL_SHARED_CHAT_END.value: CHANNEL_SHARED_CHAT_END, + CHANNEL_SHIELD_MODE_BEGIN.value: CHANNEL_SHIELD_MODE_BEGIN, + CHANNEL_SHIELD_MODE_END.value: CHANNEL_SHIELD_MODE_END, + CHANNEL_SHOUTOUT_CREATE.value: CHANNEL_SHOUTOUT_CREATE, + CHANNEL_SHOUTOUT_RECEIVE.value: CHANNEL_SHOUTOUT_RECEIVE, + CONDUIT_SHARD_DISABLED.value: CONDUIT_SHARD_DISABLED, + DROP_ENTITLEMENT_GRANT.value: DROP_ENTITLEMENT_GRANT, + EXTENSION_BITS_TRANSACTION_CREATE.value: EXTENSION_BITS_TRANSACTION_CREATE, + CHANNEL_GOAL_BEGIN.value: CHANNEL_GOAL_BEGIN, + CHANNEL_GOAL_PROGRESS.value: CHANNEL_GOAL_PROGRESS, + CHANNEL_GOAL_END.value: CHANNEL_GOAL_END, + STREAM_ONLINE.value: STREAM_ONLINE, + STREAM_OFFLINE.value: STREAM_OFFLINE, + USER_AUTHORIZATION_GRANT.value: USER_AUTHORIZATION_GRANT, + USER_AUTHORIZATION_REVOKE.value: USER_AUTHORIZATION_REVOKE, + USER_UPDATE.value: USER_UPDATE, + USER_WHISPER_MESSAGE.value: USER_WHISPER_MESSAGE, +} diff --git a/addons/twitcher/eventsub/twitch_eventsub_definition.gd.uid b/addons/twitcher/eventsub/twitch_eventsub_definition.gd.uid new file mode 100644 index 00000000..926b47f9 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_eventsub_definition.gd.uid @@ -0,0 +1 @@ +uid://cxmnkr64wc2hx diff --git a/addons/twitcher/eventsub/twitch_keepalive_message.gd b/addons/twitcher/eventsub/twitch_keepalive_message.gd new file mode 100644 index 00000000..cf48d52b --- /dev/null +++ b/addons/twitcher/eventsub/twitch_keepalive_message.gd @@ -0,0 +1,13 @@ +extends RefCounted + +## Defines the message that the EventSub WebSocket server sends your client to indicate that the WebSocket connection is healthy. +## See: https://dev.twitch.tv/docs/eventsub/websocket-reference/#keepalive-message +class_name TwitchKeepaliveMessage + +var metadata: TwitchEventsub.Metadata; +## Is always empty +var payload: Dictionary = {}; + +func _init(d: Dictionary) -> void: + metadata = TwitchEventsub.Metadata.new(d.get("metadata", {})); + payload = d["payload"]; diff --git a/addons/twitcher/eventsub/twitch_keepalive_message.gd.uid b/addons/twitcher/eventsub/twitch_keepalive_message.gd.uid new file mode 100644 index 00000000..55111c7f --- /dev/null +++ b/addons/twitcher/eventsub/twitch_keepalive_message.gd.uid @@ -0,0 +1 @@ +uid://dygbx208p487l diff --git a/addons/twitcher/eventsub/twitch_notification_message.gd b/addons/twitcher/eventsub/twitch_notification_message.gd new file mode 100644 index 00000000..48b194a1 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_notification_message.gd @@ -0,0 +1,79 @@ +extends RefCounted + +class_name TwitchNotificationMessage + +## An object that identifies the message. +class Metadata extends TwitchEventsub.Metadata: + ## The type of event sent in the message. + var subscription_type: String; + ## The version number of the subscription type’s definition. This is the same value specified in the subscription request. + var subscription_version: String; + + func _init(d: Dictionary) -> void: + super._init(d); + subscription_type = d["subscription_type"]; + subscription_version = d["subscription_version"]; + +## An object that contains the message. +class Payload extends RefCounted: + ## An object that contains information about your subscription. + var subscription: Subscription; + ## The event’s data. For information about the event’s data, see the subscription type’s description in Subscription Types. https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types + var event: Dictionary; + + func _init(d: Dictionary) -> void: + var sub = d.get("subscription"); + if sub != null: + subscription = Subscription.new(sub) + + event = d["event"] + +## An object that contains information about your subscription. +class Subscription extends RefCounted: + ## An ID that uniquely identifies this subscription. + var id: String; + ## The subscription’s status, which is set to enabled. + var status: String; + ## The type of event sent in the message. See the event field. + var type: String; + ## The version number of the subscription type’s definition. + var version: String; + ## The event’s cost. See Subscription limits. (https://dev.twitch.tv/docs/eventsub/manage-subscriptions#subscription-limits) + var cost: int; + ## The conditions under which the event fires. For example, if you requested notifications when a broadcaster gets a new follower, this object contains the broadcaster’s ID. For information about the condition’s data, see the subscription type’s description in Subscription types. (https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types) + var condition: Dictionary; + ## An object that contains information about the transport used for notifications. + var transport: Transport; + ## The UTC date and time that the subscription was created. + var created_at: String; + + func _init(d: Dictionary) -> void: + id = d["id"]; + status = d["status"]; + type = d["type"]; + version = d["version"]; + cost = d["cost"]; + condition = d["condition"]; + transport = Transport.new(d.get("transport", {})); + created_at = d["created_at"]; + +## An object that contains information about the transport used for notifications. +class Transport extends RefCounted: + ## The transport method, which is set to websocket. + var method: String; + ## An ID that uniquely identifies the WebSocket connection. + var session_id: String; + + func _init(d: Dictionary) -> void: + method = d["method"]; + session_id = d["session_id"]; + +## An object that identifies the message. +var metadata: Metadata; + +## An object that contains the message. +var payload: Payload; + +func _init(d: Dictionary) -> void: + metadata = Metadata.new(d["metadata"]) + payload = Payload.new(d["payload"]) diff --git a/addons/twitcher/eventsub/twitch_notification_message.gd.uid b/addons/twitcher/eventsub/twitch_notification_message.gd.uid new file mode 100644 index 00000000..51ba5bad --- /dev/null +++ b/addons/twitcher/eventsub/twitch_notification_message.gd.uid @@ -0,0 +1 @@ +uid://2xnomqtfyxcd diff --git a/addons/twitcher/eventsub/twitch_reconnect_message.gd b/addons/twitcher/eventsub/twitch_reconnect_message.gd new file mode 100644 index 00000000..0ce208a1 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_reconnect_message.gd @@ -0,0 +1,20 @@ +extends RefCounted + +class_name TwitchReconnectMessage + +## An object that contains the message. +class Payload extends RefCounted: + var session: TwitchEventsub.Session; + + func _init(d: Dictionary) -> void: + session = TwitchEventsub.Session.new(d.get("session", {})); + +## An object that identifies the message. +var metadata: TwitchEventsub.Metadata; + +## An object that contains the message. +var payload: Payload; + +func _init(d: Dictionary) -> void: + metadata = TwitchEventsub.Metadata.new(d.get("metadata", {})); + payload = Payload.new(d.get("payload", {})); diff --git a/addons/twitcher/eventsub/twitch_reconnect_message.gd.uid b/addons/twitcher/eventsub/twitch_reconnect_message.gd.uid new file mode 100644 index 00000000..1c4fd305 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_reconnect_message.gd.uid @@ -0,0 +1 @@ +uid://ciqfgatm1v2wy diff --git a/addons/twitcher/eventsub/twitch_revocation_message.gd b/addons/twitcher/eventsub/twitch_revocation_message.gd new file mode 100644 index 00000000..e9f87c67 --- /dev/null +++ b/addons/twitcher/eventsub/twitch_revocation_message.gd @@ -0,0 +1,76 @@ +extends RefCounted + +class_name TwitchRevocationMessage + +## An object that identifies the message. +class Metadata extends TwitchEventsub.Metadata: + ## The type of event sent in the message. + var subscription_type: String; + ## The version number of the subscription type’s definition. This is the same value specified in the subscription request. + var subscription_version: String; + + func _init(d: Dictionary) -> void: + super._init(d); + subscription_type = d["subscription_type"]; + subscription_version = d["subscription_version"]; + +## An object that contains the message. +class Payload extends RefCounted: + ## An object that contains information about your subscription. + var subscription: Subscription; + + func _init(d: Dictionary) -> void: + subscription = Subscription.new(d.get("subscription", {})) + +## An object that contains information about your subscription. +class Subscription extends RefCounted: + ## An ID that uniquely identifies this subscription. + var id: String; + ## he subscription's status. The following are the possible values:[br] + ## authorization_revoked — The user in the condition object revoked the authorization that let you get events on their behalf.[br] + ## user_removed — The user in the condition object is no longer a Twitch user.[br] + ## version_removed — The subscribed to subscription type and version is no longer supported. + var status: String; + ## The type of event sent in the message. + var type: String; + ## The version number of the subscription type's definition. + var version: String; + ## The event's cost. See Subscription limits (https://dev.twitch.tv/docs/eventsub/manage-subscriptions/#subscription-limits) + var cost: String; + ## The conditions under which the event fires. For example, if you requested notifications when a broadcaster gets a new follower, this object contains the broadcaster’s ID. For information about the condition's data, see the subscription type's description in Subscription Types. (https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types) + var condition: Dictionary; + ## An object that contains information about the transport used for notifications. + var transport: Transport; + ## The UTC date and time that the subscription was created. + var created_at: String; + + func _init(d: Dictionary) -> void: + id = d["id"]; + status = d["status"]; + type = d["type"]; + version = d["version"]; + cost = d["cost"]; + condition = d["condition"]; + transport = Transport.new(d.get("transport", {})); + created_at = d["created_at"]; + +## An object that contains information about the transport used for notifications. +class Transport extends RefCounted: + ## The transport method, which is set to websocket. + var method: String; + ## An ID that uniquely identifies the WebSocket connection. + var session_id: String; + + func _init(d: Dictionary) -> void: + method = d["method"]; + session_id = d["session_id"]; + +## An object that identifies the message. +var metadata: Metadata; + +## An object that contains the message. +var payload: Payload; + +func _init(d: Dictionary) -> void: + metadata = Metadata.new(d.get("metadata", {})) + payload = Payload.new(d.get("payload", {})) diff --git a/addons/twitcher/eventsub/twitch_revocation_message.gd.uid b/addons/twitcher/eventsub/twitch_revocation_message.gd.uid new file mode 100644 index 00000000..029fcd1a --- /dev/null +++ b/addons/twitcher/eventsub/twitch_revocation_message.gd.uid @@ -0,0 +1 @@ +uid://c1evlw325jeeg diff --git a/addons/twitcher/eventsub/twitch_welcome_message.gd b/addons/twitcher/eventsub/twitch_welcome_message.gd new file mode 100644 index 00000000..5d18ce1b --- /dev/null +++ b/addons/twitcher/eventsub/twitch_welcome_message.gd @@ -0,0 +1,18 @@ +extends RefCounted + +## Defines the first message that the EventSub WebSocket server sends after your client connects to the server. +## See: https://dev.twitch.tv/docs/eventsub/websocket-reference/#welcome-message +class_name TwitchWelcomeMessage + +class TwitchWelcomeMessagePayload extends RefCounted: + var session: TwitchEventsub.Session; + + func _init(d: Dictionary) -> void: + session = TwitchEventsub.Session.new(d.get("session", {})); + +var metadata: TwitchEventsub.Metadata; +var payload: TwitchWelcomeMessagePayload; + +func _init(d: Dictionary) -> void: + metadata = TwitchEventsub.Metadata.new(d.get("metadata", {})); + payload = TwitchWelcomeMessagePayload.new(d.get("payload", {})); diff --git a/addons/twitcher/eventsub/twitch_welcome_message.gd.uid b/addons/twitcher/eventsub/twitch_welcome_message.gd.uid new file mode 100644 index 00000000..7f4b5c4a --- /dev/null +++ b/addons/twitcher/eventsub/twitch_welcome_message.gd.uid @@ -0,0 +1 @@ +uid://qh68xfq2isug diff --git a/addons/twitcher/generated/README.md b/addons/twitcher/generated/README.md new file mode 100644 index 00000000..506e51fc --- /dev/null +++ b/addons/twitcher/generated/README.md @@ -0,0 +1,5 @@ +# This folder is autogenerated via `res://addons/twitcher/editor/twitch_api_generator.gd` + +All changes can be overriden very easily. +I case you want to add / change functionallity extend the classes or even better +use [composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance) diff --git a/addons/twitcher/generated/twitch_add_blocked_term.gd b/addons/twitcher/generated/twitch_add_blocked_term.gd new file mode 100644 index 00000000..3c709edb --- /dev/null +++ b/addons/twitcher/generated/twitch_add_blocked_term.gd @@ -0,0 +1,66 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchAddBlockedTerm + + + +## +## #/components/schemas/AddBlockedTermBody +class Body extends TwitchData: + + ## The word or phrase to block from being used in the broadcaster’s chat room. The term must contain a minimum of 2 characters and may contain up to a maximum of 500 characters. + ## + ## Terms may include a wildcard character (\*). The wildcard character must appear at the beginning or end of a word or set of characters. For example, \*foo or foo\*. + ## + ## If the blocked term already exists, the response contains the existing blocked term. + @export var text: String: + set(val): + text = val + track_data(&"text", val) + + + + ## Constructor with all required fields. + static func create(_text: String) -> Body: + var body: Body = Body.new() + body.text = _text + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("text", null) != null: + result.text = d["text"] + return result + + + +## +## #/components/schemas/AddBlockedTermResponse +class Response extends TwitchData: + + ## A list that contains the single blocked term that the broadcaster added. + @export var data: Array[TwitchBlockedTerm]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchBlockedTerm]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchBlockedTerm.from_json(value)) + return result + diff --git a/addons/twitcher/generated/twitch_add_blocked_term.gd.uid b/addons/twitcher/generated/twitch_add_blocked_term.gd.uid new file mode 100644 index 00000000..48ffe6bd --- /dev/null +++ b/addons/twitcher/generated/twitch_add_blocked_term.gd.uid @@ -0,0 +1 @@ +uid://crmqbuhp7k7ms diff --git a/addons/twitcher/generated/twitch_api.gd b/addons/twitcher/generated/twitch_api.gd new file mode 100644 index 00000000..e57489ae --- /dev/null +++ b/addons/twitcher/generated/twitch_api.gd @@ -0,0 +1,3909 @@ +@tool +extends Twitcher + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## Interaction with the Twitch REST API. +class_name TwitchAPI + +static var _log: TwitchLogger = TwitchLogger.new("TwitchAPI") + +static var instance: TwitchAPI + +## Maximal tries to reauthrorize before giving up the request. +const MAX_AUTH_ERRORS: int = 3 + +## Called when the API returns unauthenticated mostly cause the accesstoken is expired +signal unauthenticated + +## Called when the API returns 403 means there are permissions / scopes missing +signal unauthorized + +## To authorize against the Twitch API +@export var token: OAuthToken: + set(val): + token = val + update_configuration_warnings() +## OAuth settings needed for client information +@export var oauth_setting: OAuthSetting: + set(val): + oauth_setting = val + update_configuration_warnings() +## URI to the Twitch API +@export var api_host: String = "https://api.twitch.tv/helix" + +## Client to make HTTP requests +var client: BufferedHTTPClient + + +func _ready() -> void: + client = BufferedHTTPClient.new() + client.name = "ApiClient" + add_child(client) + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if token == null: + result.append("Please set a token to use") + if oauth_setting == null: + result.append("Please set the correct oauth settings") + return result + + +func request(path: String, method: int, body: Variant = "", content_type: String = "", error_count: int = 0) -> BufferedHTTPClient.ResponseData: + var header : Dictionary = { + "Authorization": "Bearer %s" % [await token.get_access_token()], + "Client-ID": oauth_setting.client_id + } + if content_type != "": + header["Content-Type"] = content_type + + var request_body: String = "" + if body == null || (body is String && body == ""): + request_body = "" + elif body is Object && body.has_method("to_json"): + request_body = body.to_json() + else: + request_body = JSON.stringify(body) + + var req: BufferedHTTPClient.RequestData = client.request(api_host + path, method, header, request_body) + var res: BufferedHTTPClient.ResponseData = await client.wait_for_request(req) + + # Try to fix Godot TLS Bug + if res.result == 5: + return await retry(req, res, path, method, body, content_type, error_count + 1) + + match res.response_code: + 401: # Token expired / or missing permissions + _log.e("'%s' is unauthorized. It is probably your scopes." % path) + unauthorized.emit() + 403: + _log.i("'%s' is unauthenticated. Refresh token." % path) + unauthenticated.emit() + await token.authorized + return await retry(req, res, path, method, body, content_type, error_count + 1) + return res + + +func retry(request: BufferedHTTPClient.RequestData, + response: BufferedHTTPClient.ResponseData, + path: String, + method: int, + body: Variant = "", + content_type: String = "", + error_count: int = 0) -> BufferedHTTPClient.ResponseData: + if error_count + 1 < MAX_AUTH_ERRORS: + return await request(path, method, body, content_type, error_count + 1) + else: + # Give up the request after trying multiple times and + # return an empty response with correct error code + var empty_response: BufferedHTTPClient.ResponseData = client.empty_response(request) + empty_response.response_code = response.response_code + return empty_response + + +func _handle_error(method_name: String, response: BufferedHTTPClient.ResponseData) -> void: + var error_json: String = response.response_data.get_string_from_utf8() + var error = JSON.parse_string(error_json) + push_error("Problems while calling %s: " % method_name, error["message"]) + _log.d(error_json) + + +## Converts unix timestamp to RFC 3339 (example: 2021-10-27T00:00:00Z) when passed a string uses as is +static func get_rfc_3339_date_format(time: Variant) -> String: + if typeof(time) == TYPE_INT: + var date_time = Time.get_datetime_dict_from_unix_time(time) + return "%s-%02d-%02dT%02d:%02d:%02dZ" % [date_time['year'], date_time['month'], date_time['day'], date_time['hour'], date_time['minute'], date_time['second']] + return str(time) + + + +## Starts a commercial on the specified channel. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#start-commercial +func start_commercial(body: TwitchStartCommercial.Body) -> TwitchStartCommercial.Response: + var path = "/channels/commercial?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("start_commercial", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchStartCommercial.Response = TwitchStartCommercial.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Returns ad schedule related information. +## +## broadcaster_id - Provided `broadcaster_id` must match the `user_id` in the auth token. +## +## https://dev.twitch.tv/docs/api/reference#get-ad-schedule +func get_ad_schedule(broadcaster_id: String) -> TwitchGetAdSchedule.Response: + var path = "/channels/ads?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_ad_schedule", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetAdSchedule.Response = TwitchGetAdSchedule.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Pushes back the timestamp of the upcoming automatic mid-roll ad by 5 minutes. +## +## broadcaster_id - Provided `broadcaster_id` must match the `user_id` in the auth token. +## +## https://dev.twitch.tv/docs/api/reference#snooze-next-ad +func snooze_next_ad(broadcaster_id: String) -> TwitchSnoozeNextAd.Response: + var path = "/channels/ads/schedule/snooze?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("snooze_next_ad", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchSnoozeNextAd.Response = TwitchSnoozeNextAd.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets an analytics report for one or more extensions. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-extension-analytics +func get_extension_analytics(opt: TwitchGetExtensionAnalytics.Opt) -> TwitchGetExtensionAnalytics.Response: + var path = "/analytics/extensions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("ended_at"): + path += "ended_at=" + get_rfc_3339_date_format(optionals.ended_at) + "&" + if optionals.has("extension_id"): + path += "extension_id=" + str(optionals.extension_id) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("started_at"): + path += "started_at=" + get_rfc_3339_date_format(optionals.started_at) + "&" + if optionals.has("type"): + path += "type=" + str(optionals.type) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_analytics", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionAnalytics.Response = TwitchGetExtensionAnalytics.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetExtensionAnalytics.Opt.new() + opt.after = cursor + parsed_result._next_page = get_extension_analytics.bind(opt) + + return parsed_result + + +## Gets an analytics report for one or more games. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-game-analytics +func get_game_analytics(opt: TwitchGetGameAnalytics.Opt) -> TwitchGetGameAnalytics.Response: + var path = "/analytics/games?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("ended_at"): + path += "ended_at=" + get_rfc_3339_date_format(optionals.ended_at) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("game_id"): + path += "game_id=" + str(optionals.game_id) + "&" + if optionals.has("started_at"): + path += "started_at=" + get_rfc_3339_date_format(optionals.started_at) + "&" + if optionals.has("type"): + path += "type=" + str(optionals.type) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_game_analytics", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGameAnalytics.Response = TwitchGetGameAnalytics.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetGameAnalytics.Opt.new() + opt.after = cursor + parsed_result._next_page = get_game_analytics.bind(opt) + + return parsed_result + + +## Gets the Bits leaderboard for the authenticated broadcaster. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-bits-leaderboard +func get_bits_leaderboard(opt: TwitchGetBitsLeaderboard.Opt) -> TwitchGetBitsLeaderboard.Response: + var path = "/bits/leaderboard?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("count"): + path += "count=" + str(optionals.count) + "&" + if optionals.has("period"): + path += "period=" + str(optionals.period) + "&" + if optionals.has("started_at"): + path += "started_at=" + get_rfc_3339_date_format(optionals.started_at) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_bits_leaderboard", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetBitsLeaderboard.Response = TwitchGetBitsLeaderboard.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of Cheermotes that users can use to cheer Bits. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-cheermotes +func get_cheermotes(opt: TwitchGetCheermotes.Opt) -> TwitchGetCheermotes.Response: + var path = "/bits/cheermotes?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("broadcaster_id"): + path += "broadcaster_id=" + str(optionals.broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_cheermotes", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCheermotes.Response = TwitchGetCheermotes.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets an extension’s list of transactions. +## +## extension_id - The ID of the extension whose list of transactions you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-extension-transactions +func get_extension_transactions(opt: TwitchGetExtensionTransactions.Opt, extension_id: String) -> TwitchGetExtensionTransactions.Response: + var path = "/extensions/transactions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_transactions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionTransactions.Response = TwitchGetExtensionTransactions.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetExtensionTransactions.Opt.new() + opt.after = cursor + parsed_result._next_page = get_extension_transactions.bind(opt, extension_id) + + return parsed_result + + +## Gets information about one or more channels. +## +## broadcaster_id - The ID of the broadcaster whose channel you want to get. To specify more than one ID, include this parameter for each broadcaster you want to get. For example, `broadcaster_id=1234&broadcaster_id=5678`. You may specify a maximum of 100 IDs. The API ignores duplicate IDs and IDs that are not found. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-information +func get_channel_information(broadcaster_id: Array[String]) -> TwitchGetChannelInformation.Response: + var path = "/channels?" + + for param in broadcaster_id: + path += "broadcaster_id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_information", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelInformation.Response = TwitchGetChannelInformation.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates a channel’s properties. +## +## broadcaster_id - The ID of the broadcaster whose channel you want to update. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#modify-channel-information +func modify_channel_information(body: TwitchModifyChannelInformation.Body, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/channels?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + if response.response_code >= 400: + _handle_error("modify_channel_information", response) + return response + + +## Gets the broadcaster’s list editors. +## +## broadcaster_id - The ID of the broadcaster that owns the channel. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-editors +func get_channel_editors(broadcaster_id: String) -> TwitchGetChannelEditors.Response: + var path = "/channels/editors?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_editors", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelEditors.Response = TwitchGetChannelEditors.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of broadcasters that the specified user follows. You can also use this endpoint to see whether a user follows a specific broadcaster. +## +## user_id - A user’s ID. Returns the list of broadcasters that this user follows. This ID must match the user ID in the user OAuth token. +## +## https://dev.twitch.tv/docs/api/reference#get-followed-channels +func get_followed_channels(opt: TwitchGetFollowedChannels.Opt, user_id: String) -> TwitchGetFollowedChannels.Response: + var path = "/channels/followed?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "user_id=" + str(user_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("broadcaster_id"): + path += "broadcaster_id=" + str(optionals.broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_followed_channels", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetFollowedChannels.Response = TwitchGetFollowedChannels.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetFollowedChannels.Opt.new() + opt.after = cursor + parsed_result._next_page = get_followed_channels.bind(opt, user_id) + + return parsed_result + + +## Gets a list of users that follow the specified broadcaster. You can also use this endpoint to see whether a specific user follows the broadcaster. +## +## broadcaster_id - The broadcaster’s ID. Returns the list of users that follow this broadcaster. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-followers +func get_channel_followers(opt: TwitchGetChannelFollowers.Opt, broadcaster_id: String) -> TwitchGetChannelFollowers.Response: + var path = "/channels/followers?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_followers", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelFollowers.Response = TwitchGetChannelFollowers.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetChannelFollowers.Opt.new() + opt.after = cursor + parsed_result._next_page = get_channel_followers.bind(opt, broadcaster_id) + + return parsed_result + + +## Creates a Custom Reward in the broadcaster’s channel. +## +## broadcaster_id - The ID of the broadcaster to add the custom reward to. This ID must match the user ID found in the OAuth token. +## +## https://dev.twitch.tv/docs/api/reference#create-custom-rewards +func create_custom_rewards(body: TwitchCreateCustomRewards.Body, broadcaster_id: String) -> TwitchCreateCustomRewards.Response: + var path = "/channel_points/custom_rewards?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_custom_rewards", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateCustomRewards.Response = TwitchCreateCustomRewards.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Deletes a custom reward that the broadcaster created. +## +## broadcaster_id - The ID of the broadcaster that created the custom reward. This ID must match the user ID found in the OAuth token. +## id - The ID of the custom reward to delete. +## +## https://dev.twitch.tv/docs/api/reference#delete-custom-reward +func delete_custom_reward(id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/channel_points/custom_rewards?" + path += "id=" + str(id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_custom_reward", response) + return response + + +## Gets a list of custom rewards that the specified broadcaster created. +## +## broadcaster_id - The ID of the broadcaster whose custom rewards you want to get. This ID must match the user ID found in the OAuth token. +## +## https://dev.twitch.tv/docs/api/reference#get-custom-reward +func get_custom_reward(opt: TwitchGetCustomReward.Opt, broadcaster_id: String) -> TwitchGetCustomReward.Response: + var path = "/channel_points/custom_rewards?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("only_manageable_rewards"): + path += "only_manageable_rewards=" + str(optionals.only_manageable_rewards) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_custom_reward", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCustomReward.Response = TwitchGetCustomReward.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates a custom reward. +## +## broadcaster_id - The ID of the broadcaster that’s updating the reward. This ID must match the user ID found in the OAuth token. +## id - The ID of the reward to update. +## +## https://dev.twitch.tv/docs/api/reference#update-custom-reward +func update_custom_reward(body: TwitchUpdateCustomReward.Body, id: String, broadcaster_id: String) -> TwitchUpdateCustomReward.Response: + var path = "/channel_points/custom_rewards?" + path += "id=" + str(id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_custom_reward", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateCustomReward.Response = TwitchUpdateCustomReward.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of redemptions for a custom reward. +## +## broadcaster_id - The ID of the broadcaster that owns the custom reward. This ID must match the user ID found in the user OAuth token. +## reward_id - The ID that identifies the custom reward whose redemptions you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-custom-reward-redemption +func get_custom_reward_redemption(opt: TwitchGetCustomRewardRedemption.Opt, reward_id: String, broadcaster_id: String) -> TwitchGetCustomRewardRedemption.Response: + var path = "/channel_points/custom_rewards/redemptions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "reward_id=" + str(reward_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("sort"): + path += "sort=" + str(optionals.sort) + "&" + if optionals.has("status"): + path += "status=" + str(optionals.status) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_custom_reward_redemption", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCustomRewardRedemption.Response = TwitchGetCustomRewardRedemption.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetCustomRewardRedemption.Opt.new() + opt.after = cursor + parsed_result._next_page = get_custom_reward_redemption.bind(opt, reward_id, broadcaster_id) + + return parsed_result + + +## Updates a redemption’s status. +## +## id - A list of IDs that identify the redemptions to update. To specify more than one ID, include this parameter for each redemption you want to update. For example, `id=1234&id=5678`. You may specify a maximum of 50 IDs. +## broadcaster_id - The ID of the broadcaster that’s updating the redemption. This ID must match the user ID in the user access token. +## reward_id - The ID that identifies the reward that’s been redeemed. +## +## https://dev.twitch.tv/docs/api/reference#update-redemption-status +func update_redemption_status(body: TwitchUpdateRedemptionStatus.Body, id: Array[String], reward_id: String, broadcaster_id: String) -> TwitchUpdateRedemptionStatus.Response: + var path = "/channel_points/custom_rewards/redemptions?" + + for param in id: + path += "id=" + str(param) + "&" + path += "reward_id=" + str(reward_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_redemption_status", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateRedemptionStatus.Response = TwitchUpdateRedemptionStatus.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about the broadcaster’s active charity campaign. +## +## broadcaster_id - The ID of the broadcaster that’s currently running a charity campaign. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-charity-campaign +func get_charity_campaign(broadcaster_id: String) -> TwitchGetCharityCampaign.Response: + var path = "/charity/campaigns?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_charity_campaign", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCharityCampaign.Response = TwitchGetCharityCampaign.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the list of donations that users have made to the broadcaster’s active charity campaign. +## +## broadcaster_id - The ID of the broadcaster that’s currently running a charity campaign. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-charity-campaign-donations +func get_charity_campaign_donations(opt: TwitchGetCharityCampaignDonations.Opt, broadcaster_id: String) -> TwitchGetCharityCampaignDonations.Response: + var path = "/charity/donations?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_charity_campaign_donations", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCharityCampaignDonations.Response = TwitchGetCharityCampaignDonations.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetCharityCampaignDonations.Opt.new() + opt.after = cursor + parsed_result._next_page = get_charity_campaign_donations.bind(opt, broadcaster_id) + + return parsed_result + + +## Gets the list of users that are connected to the broadcaster’s chat session. +## +## broadcaster_id - The ID of the broadcaster whose list of chatters you want to get. +## moderator_id - The ID of the broadcaster or one of the broadcaster’s moderators. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-chatters +func get_chatters(opt: TwitchGetChatters.Opt, moderator_id: String, broadcaster_id: String) -> TwitchGetChatters.Response: + var path = "/chat/chatters?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_chatters", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChatters.Response = TwitchGetChatters.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetChatters.Opt.new() + opt.after = cursor + parsed_result._next_page = get_chatters.bind(opt, moderator_id, broadcaster_id) + + return parsed_result + + +## Gets the broadcaster’s list of custom emotes. +## +## broadcaster_id - An ID that identifies the broadcaster whose emotes you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-emotes +func get_channel_emotes(broadcaster_id: String) -> TwitchGetChannelEmotes.Response: + var path = "/chat/emotes?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_emotes", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelEmotes.Response = TwitchGetChannelEmotes.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets all global emotes. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-global-emotes +func get_global_emotes() -> TwitchGetGlobalEmotes.Response: + var path = "/chat/emotes/global?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_global_emotes", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGlobalEmotes.Response = TwitchGetGlobalEmotes.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets emotes for one or more specified emote sets. +## +## emote_set_id - An ID that identifies the emote set to get. Include this parameter for each emote set you want to get. For example, `emote_set_id=1234&emote_set_id=5678`. You may specify a maximum of 25 IDs. The response contains only the IDs that were found and ignores duplicate IDs. +## +## To get emote set IDs, use the [Get Channel Emotes](https://dev.twitch.tv/docs/api/reference#get-channel-emotes) API. +## +## https://dev.twitch.tv/docs/api/reference#get-emote-sets +func get_emote_sets(emote_set_id: Array[String]) -> TwitchGetEmoteSets.Response: + var path = "/chat/emotes/set?" + + for param in emote_set_id: + path += "emote_set_id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_emote_sets", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetEmoteSets.Response = TwitchGetEmoteSets.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the broadcaster’s list of custom chat badges. +## +## broadcaster_id - The ID of the broadcaster whose chat badges you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-chat-badges +func get_channel_chat_badges(broadcaster_id: String) -> TwitchGetChannelChatBadges.Response: + var path = "/chat/badges?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_chat_badges", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelChatBadges.Response = TwitchGetChannelChatBadges.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets Twitch’s list of chat badges. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-global-chat-badges +func get_global_chat_badges() -> TwitchGetGlobalChatBadges.Response: + var path = "/chat/badges/global?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_global_chat_badges", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGlobalChatBadges.Response = TwitchGetGlobalChatBadges.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the broadcaster’s chat settings. +## +## broadcaster_id - The ID of the broadcaster whose chat settings you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-chat-settings +func get_chat_settings(opt: TwitchGetChatSettings.Opt, broadcaster_id: String) -> TwitchGetChatSettings.Response: + var path = "/chat/settings?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("moderator_id"): + path += "moderator_id=" + str(optionals.moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_chat_settings", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChatSettings.Response = TwitchGetChatSettings.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates the broadcaster’s chat settings. +## +## broadcaster_id - The ID of the broadcaster whose chat settings you want to update. +## moderator_id - The ID of a user that has permission to moderate the broadcaster’s chat room, or the broadcaster’s ID if they’re making the update. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#update-chat-settings +func update_chat_settings(body: TwitchUpdateChatSettings.Body, moderator_id: String, broadcaster_id: String) -> TwitchUpdateChatSettings.Response: + var path = "/chat/settings?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_chat_settings", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateChatSettings.Response = TwitchUpdateChatSettings.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Retrieves the active shared chat session for a channel. +## +## broadcaster_id - The User ID of the channel broadcaster. +## +## https://dev.twitch.tv/docs/api/reference#get-shared-chat-session +func get_shared_chat_session(broadcaster_id: String) -> TwitchGetSharedChatSession.Response: + var path = "/shared_chat/session?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_shared_chat_session", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetSharedChatSession.Response = TwitchGetSharedChatSession.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Retrieves emotes available to the user across all channels. +## +## user_id - The ID of the user. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-user-emotes +func get_user_emotes(opt: TwitchGetUserEmotes.Opt, user_id: String) -> TwitchGetUserEmotes.Response: + var path = "/chat/emotes/user?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "user_id=" + str(user_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("broadcaster_id"): + path += "broadcaster_id=" + str(optionals.broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_user_emotes", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUserEmotes.Response = TwitchGetUserEmotes.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetUserEmotes.Opt.new() + opt.after = cursor + parsed_result._next_page = get_user_emotes.bind(opt, user_id) + + return parsed_result + + +## Sends an announcement to the broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster that owns the chat room to send the announcement to. +## moderator_id - The ID of a user who has permission to moderate the broadcaster’s chat room, or the broadcaster’s ID if they’re sending the announcement. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#send-chat-announcement +func send_chat_announcement(body: TwitchSendChatAnnouncement.Body, moderator_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/chat/announcements?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + if response.response_code >= 400: + _handle_error("send_chat_announcement", response) + return response + + +## Sends a Shoutout to the specified broadcaster. +## +## from_broadcaster_id - The ID of the broadcaster that’s sending the Shoutout. +## to_broadcaster_id - The ID of the broadcaster that’s receiving the Shoutout. +## moderator_id - The ID of the broadcaster or a user that is one of the broadcaster’s moderators. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#send-a-shoutout +func send_a_shoutout(from_broadcaster_id: String, moderator_id: String, to_broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/chat/shoutouts?" + path += "from_broadcaster_id=" + str(from_broadcaster_id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "to_broadcaster_id=" + str(to_broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + if response.response_code >= 400: + _handle_error("send_a_shoutout", response) + return response + + +## NEW Sends a message to the broadcaster’s chat room. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#send-chat-message +func send_chat_message(body: TwitchSendChatMessage.Body) -> TwitchSendChatMessage.Response: + var path = "/chat/messages?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("send_chat_message", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchSendChatMessage.Response = TwitchSendChatMessage.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the color used for the user’s name in chat. +## +## user_id - The ID of the user whose username color you want to get. To specify more than one user, include the _user\_id_ parameter for each user to get. For example, `&user_id=1234&user_id=5678`. The maximum number of IDs that you may specify is 100. +## +## The API ignores duplicate IDs and IDs that weren’t found. +## +## https://dev.twitch.tv/docs/api/reference#get-user-chat-color +func get_user_chat_color(user_id: Array[String]) -> TwitchGetUserChatColor.Response: + var path = "/chat/color?" + + for param in user_id: + path += "user_id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_user_chat_color", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUserChatColor.Response = TwitchGetUserChatColor.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates the color used for the user’s name in chat. +## +## user_id - The ID of the user whose chat color you want to update. This ID must match the user ID in the access token. +## color - The color to use for the user's name in chat. All users may specify one of the following named color values. +## +## * blue +## * blue\_violet +## * cadet\_blue +## * chocolate +## * coral +## * dodger\_blue +## * firebrick +## * golden\_rod +## * green +## * hot\_pink +## * orange\_red +## * red +## * sea\_green +## * spring\_green +## * yellow\_green +## +## Turbo and Prime users may specify a named color or a Hex color code like #9146FF. If you use a Hex color code, remember to URL encode it. +## +## https://dev.twitch.tv/docs/api/reference#update-user-chat-color +func update_user_chat_color(color: String, user_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/chat/color?" + path += "color=" + str(color) + "&" + path += "user_id=" + str(user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, "", "") + + if response.response_code >= 400: + _handle_error("update_user_chat_color", response) + return response + + +## Creates a clip from the broadcaster’s stream. +## +## broadcaster_id - The ID of the broadcaster whose stream you want to create a clip from. +## +## https://dev.twitch.tv/docs/api/reference#create-clip +func create_clip(opt: TwitchCreateClip.Opt, broadcaster_id: String) -> TwitchCreateClip.Response: + var path = "/clips?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("has_delay"): + path += "has_delay=" + str(optionals.has_delay) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_clip", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateClip.Response = TwitchCreateClip.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets one or more video clips. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-clips +func get_clips(opt: TwitchGetClips.Opt) -> TwitchGetClips.Response: + var path = "/clips?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("ended_at"): + path += "ended_at=" + get_rfc_3339_date_format(optionals.ended_at) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("game_id"): + path += "game_id=" + str(optionals.game_id) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("is_featured"): + path += "is_featured=" + str(optionals.is_featured) + "&" + if optionals.has("started_at"): + path += "started_at=" + get_rfc_3339_date_format(optionals.started_at) + "&" + if optionals.has("broadcaster_id"): + path += "broadcaster_id=" + str(optionals.broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_clips", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetClips.Response = TwitchGetClips.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetClips.Opt.new() + opt.after = cursor + parsed_result._next_page = get_clips.bind(opt) + + return parsed_result + + +## NEW Gets the conduits for a client ID. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-conduits +func get_conduits() -> TwitchGetConduits.Response: + var path = "/eventsub/conduits?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_conduits", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetConduits.Response = TwitchGetConduits.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Creates a new conduit. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#create-conduits +func create_conduits(body: TwitchCreateConduits.Body) -> TwitchCreateConduits.Response: + var path = "/eventsub/conduits?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_conduits", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateConduits.Response = TwitchCreateConduits.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Updates a conduit’s shard count. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-conduits +func update_conduits(body: TwitchUpdateConduits.Body) -> TwitchUpdateConduits.Response: + var path = "/eventsub/conduits?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_conduits", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateConduits.Response = TwitchUpdateConduits.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Deletes a specified conduit. +## +## id - Conduit ID. +## +## https://dev.twitch.tv/docs/api/reference#delete-conduit +func delete_conduit(id: String) -> BufferedHTTPClient.ResponseData: + var path = "/eventsub/conduits?" + path += "id=" + str(id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_conduit", response) + return response + + +## NEW Gets a lists of all shards for a conduit. +## +## conduit_id - Conduit ID. +## +## https://dev.twitch.tv/docs/api/reference#get-conduit-shards +func get_conduit_shards(opt: TwitchGetConduitShards.Opt, conduit_id: String) -> TwitchGetConduitShards.Response: + var path = "/eventsub/conduits/shards?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "conduit_id=" + str(conduit_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("status"): + path += "status=" + str(optionals.status) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_conduit_shards", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetConduitShards.Response = TwitchGetConduitShards.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetConduitShards.Opt.new() + opt.after = cursor + parsed_result._next_page = get_conduit_shards.bind(opt, conduit_id) + + return parsed_result + + +## NEW Updates shard(s) for a conduit. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-conduit-shards +func update_conduit_shards(body: TwitchUpdateConduitShards.Body) -> TwitchUpdateConduitShards.Response: + var path = "/eventsub/conduits/shards?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_conduit_shards", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateConduitShards.Response = TwitchUpdateConduitShards.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about Twitch content classification labels. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-content-classification-labels +func get_content_classification_labels(opt: TwitchGetContentClassificationLabels.Opt) -> TwitchGetContentClassificationLabels.Response: + var path = "/content_classification_labels?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("locale"): + path += "locale=" + str(optionals.locale) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_content_classification_labels", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetContentClassificationLabels.Response = TwitchGetContentClassificationLabels.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets an organization’s list of entitlements that have been granted to a game, a user, or both. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-drops-entitlements +func get_drops_entitlements(opt: TwitchGetDropsEntitlements.Opt) -> TwitchGetDropsEntitlements.Response: + var path = "/entitlements/drops?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("fulfillment_status"): + path += "fulfillment_status=" + str(optionals.fulfillment_status) + "&" + if optionals.has("game_id"): + path += "game_id=" + str(optionals.game_id) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_drops_entitlements", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetDropsEntitlements.Response = TwitchGetDropsEntitlements.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetDropsEntitlements.Opt.new() + opt.after = cursor + parsed_result._next_page = get_drops_entitlements.bind(opt) + + return parsed_result + + +## Updates the Drop entitlement’s fulfillment status. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-drops-entitlements +func update_drops_entitlements(body: TwitchUpdateDropsEntitlements.Body) -> TwitchUpdateDropsEntitlements.Response: + var path = "/entitlements/drops?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_drops_entitlements", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateDropsEntitlements.Response = TwitchUpdateDropsEntitlements.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the specified configuration segment from the specified extension. +## +## extension_id - The ID of the extension that contains the configuration segment you want to get. +## segment - The type of configuration segment to get. Possible case-sensitive values are: +## +## * broadcaster +## * developer +## * global +## +## You may specify one or more segments. To specify multiple segments, include the `segment` parameter for each segment to get. For example, `segment=broadcaster&segment=developer`. Ignores duplicate segments. +## +## https://dev.twitch.tv/docs/api/reference#get-extension-configuration-segment +func get_extension_configuration_segment(opt: TwitchGetExtensionConfigurationSegment.Opt, extension_id: String, segment: String) -> TwitchGetExtensionConfigurationSegment.Response: + var path = "/extensions/configurations?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + path += "segment=" + str(segment) + "&" + if optionals.has("broadcaster_id"): + path += "broadcaster_id=" + str(optionals.broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_configuration_segment", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionConfigurationSegment.Response = TwitchGetExtensionConfigurationSegment.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates a configuration segment. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#set-extension-configuration-segment +func set_extension_configuration_segment(body: TwitchSetExtensionConfigurationSegment.Body) -> BufferedHTTPClient.ResponseData: + var path = "/extensions/configurations?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + if response.response_code >= 400: + _handle_error("set_extension_configuration_segment", response) + return response + + +## Updates the extension’s required_configuration string. +## +## broadcaster_id - The ID of the broadcaster that installed the extension on their channel. +## +## https://dev.twitch.tv/docs/api/reference#set-extension-required-configuration +func set_extension_required_configuration(body: TwitchSetExtensionRequiredConfiguration.Body, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/extensions/required_configuration?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + if response.response_code >= 400: + _handle_error("set_extension_required_configuration", response) + return response + + +## Sends a message to one or more viewers. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#send-extension-pubsub-message +func send_extension_pubsub_message(body: TwitchSendExtensionPubSubMessage.Body) -> BufferedHTTPClient.ResponseData: + var path = "/extensions/pubsub?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + if response.response_code >= 400: + _handle_error("send_extension_pubsub_message", response) + return response + + +## Gets a list of broadcasters that are streaming live and have installed or activated the extension. +## +## extension_id - The ID of the extension to get. Returns the list of broadcasters that are live and that have installed or activated this extension. +## +## https://dev.twitch.tv/docs/api/reference#get-extension-live-channels +func get_extension_live_channels(opt: TwitchGetExtensionLiveChannels.Opt, extension_id: String) -> TwitchGetExtensionLiveChannels.Response: + var path = "/extensions/live?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_live_channels", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionLiveChannels.Response = TwitchGetExtensionLiveChannels.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination + if not opt: opt = TwitchGetExtensionLiveChannels.Opt.new() + opt.after = cursor + parsed_result._next_page = get_extension_live_channels.bind(opt, extension_id) + + return parsed_result + + +## Gets an extension’s list of shared secrets. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-extension-secrets +func get_extension_secrets() -> TwitchGetExtensionSecrets.Response: + var path = "/extensions/jwt/secrets?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_secrets", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionSecrets.Response = TwitchGetExtensionSecrets.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Creates a shared secret used to sign and verify JWT tokens. +## +## extension_id - The ID of the extension to apply the shared secret to. +## +## https://dev.twitch.tv/docs/api/reference#create-extension-secret +func create_extension_secret(opt: TwitchCreateExtensionSecret.Opt, extension_id: String) -> TwitchCreateExtensionSecret.Response: + var path = "/extensions/jwt/secrets?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + if optionals.has("delay"): + path += "delay=" + str(optionals.delay) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_extension_secret", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateExtensionSecret.Response = TwitchCreateExtensionSecret.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Sends a message to the specified broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster that has activated the extension. +## +## https://dev.twitch.tv/docs/api/reference#send-extension-chat-message +func send_extension_chat_message(body: TwitchSendExtensionChatMessage.Body, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/extensions/chat?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + if response.response_code >= 400: + _handle_error("send_extension_chat_message", response) + return response + + +## Gets information about an extension. +## +## extension_id - The ID of the extension to get. +## +## https://dev.twitch.tv/docs/api/reference#get-extensions +func get_extensions(opt: TwitchGetExtensions.Opt, extension_id: String) -> TwitchGetExtensions.Response: + var path = "/extensions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + if optionals.has("extension_version"): + path += "extension_version=" + str(optionals.extension_version) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extensions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensions.Response = TwitchGetExtensions.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about a released extension. +## +## extension_id - The ID of the extension to get. +## +## https://dev.twitch.tv/docs/api/reference#get-released-extensions +func get_released_extensions(opt: TwitchGetReleasedExtensions.Opt, extension_id: String) -> TwitchGetReleasedExtensions.Response: + var path = "/extensions/released?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "extension_id=" + str(extension_id) + "&" + if optionals.has("extension_version"): + path += "extension_version=" + str(optionals.extension_version) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_released_extensions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetReleasedExtensions.Response = TwitchGetReleasedExtensions.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the list of Bits products that belongs to the extension. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-extension-bits-products +func get_extension_bits_products(opt: TwitchGetExtensionBitsProducts.Opt) -> TwitchGetExtensionBitsProducts.Response: + var path = "/bits/extensions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("should_include_all"): + path += "should_include_all=" + str(optionals.should_include_all) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_extension_bits_products", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetExtensionBitsProducts.Response = TwitchGetExtensionBitsProducts.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Adds or updates a Bits product that the extension created. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-extension-bits-product +func update_extension_bits_product(body: TwitchUpdateExtensionBitsProduct.Body) -> TwitchUpdateExtensionBitsProduct.Response: + var path = "/bits/extensions?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_extension_bits_product", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateExtensionBitsProduct.Response = TwitchUpdateExtensionBitsProduct.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Creates an EventSub subscription. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#create-eventsub-subscription +func create_eventsub_subscription(body: TwitchCreateEventSubSubscription.Body) -> TwitchCreateEventSubSubscription.Response: + var path = "/eventsub/subscriptions?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_eventsub_subscription", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateEventSubSubscription.Response = TwitchCreateEventSubSubscription.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Deletes an EventSub subscription. +## +## id - The ID of the subscription to delete. +## +## https://dev.twitch.tv/docs/api/reference#delete-eventsub-subscription +func delete_eventsub_subscription(id: String) -> BufferedHTTPClient.ResponseData: + var path = "/eventsub/subscriptions?" + path += "id=" + str(id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_eventsub_subscription", response) + return response + + +## Gets a list of EventSub subscriptions that the client in the access token created. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-eventsub-subscriptions +func get_eventsub_subscriptions(opt: TwitchGetEventsubSubscriptions.Opt) -> TwitchGetEventSubSubscriptions.Response: + var path = "/eventsub/subscriptions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("status"): + path += "status=" + str(optionals.status) + "&" + if optionals.has("subscription_id"): + path += "subscription_id=" + str(optionals.subscription_id) + "&" + if optionals.has("type"): + path += "type=" + str(optionals.type) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_eventsub_subscriptions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetEventSubSubscriptions.Response = TwitchGetEventSubSubscriptions.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetEventsubSubscriptions.Opt.new() + opt.after = cursor + parsed_result._next_page = get_eventsub_subscriptions.bind(opt) + + return parsed_result + + +## Gets information about all broadcasts on Twitch. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-top-games +func get_top_games(opt: TwitchGetTopGames.Opt) -> TwitchGetTopGames.Response: + var path = "/games/top?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_top_games", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetTopGames.Response = TwitchGetTopGames.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetTopGames.Opt.new() + opt.after = cursor + parsed_result._next_page = get_top_games.bind(opt) + + return parsed_result + + +## Gets information about specified games. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-games +func get_games(opt: TwitchGetGames.Opt) -> TwitchGetGames.Response: + var path = "/games?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("igdb_id"): + + for param in optionals.igdb_id: + path += "igdb_id=" + str(param) + "&" + if optionals.has("name"): + + for param in optionals.name: + path += "name=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_games", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGames.Response = TwitchGetGames.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the broadcaster’s list of active goals. +## +## broadcaster_id - The ID of the broadcaster that created the goals. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-creator-goals +func get_creator_goals(broadcaster_id: String) -> TwitchGetCreatorGoals.Response: + var path = "/goals?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_creator_goals", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetCreatorGoals.Response = TwitchGetCreatorGoals.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## BETA Gets the channel settings for configuration of the Guest Star feature for a particular host. +## +## broadcaster_id - The ID of the broadcaster you want to get guest star settings for. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-guest-star-settings +func get_channel_guest_star_settings(moderator_id: String, broadcaster_id: String) -> TwitchGetChannelGuestStarSettings.Response: + var path = "/guest_star/channel_settings?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_guest_star_settings", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelGuestStarSettings.Response = TwitchGetChannelGuestStarSettings.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## BETA Mutates the channel settings for configuration of the Guest Star feature for a particular host. +## +## broadcaster_id - The ID of the broadcaster you want to update Guest Star settings for. +## +## https://dev.twitch.tv/docs/api/reference#update-channel-guest-star-settings +func update_channel_guest_star_settings(body: TwitchUpdateChannelGuestStarSettings.Body, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/channel_settings?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + if response.response_code >= 400: + _handle_error("update_channel_guest_star_settings", response) + return response + + +## BETA Gets information about an ongoing Guest Star session for a particular channel. +## +## broadcaster_id - ID for the user hosting the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-guest-star-session +func get_guest_star_session(moderator_id: String, broadcaster_id: String) -> TwitchGetGuestStarSession.Response: + var path = "/guest_star/session?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_guest_star_session", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGuestStarSession.Response = TwitchGetGuestStarSession.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## BETA Programmatically creates a Guest Star session on behalf of the broadcaster. +## +## broadcaster_id - The ID of the broadcaster you want to create a Guest Star session for. Provided `broadcaster_id` must match the `user_id` in the auth token. +## +## https://dev.twitch.tv/docs/api/reference#create-guest-star-session +func create_guest_star_session(broadcaster_id: String) -> TwitchCreateGuestStarSession.Response: + var path = "/guest_star/session?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_guest_star_session", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateGuestStarSession.Response = TwitchCreateGuestStarSession.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## BETA Programmatically ends a Guest Star session on behalf of the broadcaster. +## +## broadcaster_id - The ID of the broadcaster you want to end a Guest Star session for. Provided `broadcaster_id` must match the `user_id` in the auth token. +## session_id - ID for the session to end on behalf of the broadcaster. +## +## https://dev.twitch.tv/docs/api/reference#end-guest-star-session +func end_guest_star_session(session_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/session?" + path += "session_id=" + str(session_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("end_guest_star_session", response) + return response + + +## BETA Provides the caller with a list of pending invites to a Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the `user_id` in the user access token. +## session_id - The session ID to query for invite status. +## +## https://dev.twitch.tv/docs/api/reference#get-guest-star-invites +func get_guest_star_invites(moderator_id: String, session_id: String, broadcaster_id: String) -> TwitchGetGuestStarInvites.Response: + var path = "/guest_star/invites?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_guest_star_invites", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetGuestStarInvites.Response = TwitchGetGuestStarInvites.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## BETA Sends an invite to a specified guest on behalf of the broadcaster for a Guest Star session in progress. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the `user_id` in the user access token. +## session_id - The session ID for the invite to be sent on behalf of the broadcaster. +## guest_id - Twitch User ID for the guest to invite to the Guest Star session. +## +## https://dev.twitch.tv/docs/api/reference#send-guest-star-invite +func send_guest_star_invite(guest_id: String, moderator_id: String, session_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/invites?" + path += "guest_id=" + str(guest_id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + if response.response_code >= 400: + _handle_error("send_guest_star_invite", response) + return response + + +## BETA Revokes a previously sent invite for a Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the `user_id` in the user access token. +## session_id - The ID of the session for the invite to be revoked on behalf of the broadcaster. +## guest_id - Twitch User ID for the guest to revoke the Guest Star session invite from. +## +## https://dev.twitch.tv/docs/api/reference#delete-guest-star-invite +func delete_guest_star_invite(guest_id: String, moderator_id: String, session_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/invites?" + path += "guest_id=" + str(guest_id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_guest_star_invite", response) + return response + + +## BETA Allows a previously invited user to be assigned a slot within the active Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the `user_id` in the user access token. +## session_id - The ID of the Guest Star session in which to assign the slot. +## guest_id - The Twitch User ID corresponding to the guest to assign a slot in the session. This user must already have an invite to this session, and have indicated that they are ready to join. +## slot_id - The slot assignment to give to the user. Must be a numeric identifier between “1” and “N” where N is the max number of slots for the session. Max number of slots allowed for the session is reported by [Get Channel Guest Star Settings](https://dev.twitch.tv/docs/api/reference#get-channel-guest-star-settings). +## +## https://dev.twitch.tv/docs/api/reference#assign-guest-star-slot +func assign_guest_star_slot(guest_id: String, moderator_id: String, session_id: String, slot_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/slot?" + path += "guest_id=" + str(guest_id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "slot_id=" + str(slot_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + if response.response_code >= 400: + _handle_error("assign_guest_star_slot", response) + return response + + +## BETA Allows a user to update the assigned slot for a particular user within the active Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the `user_id` in the user access token. +## session_id - The ID of the Guest Star session in which to update slot settings. +## source_slot_id - The slot assignment previously assigned to a user. +## +## https://dev.twitch.tv/docs/api/reference#update-guest-star-slot +func update_guest_star_slot(opt: TwitchUpdateGuestStarSlot.Opt, moderator_id: String, session_id: String, source_slot_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/slot?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "source_slot_id=" + str(source_slot_id) + "&" + if optionals.has("destination_slot_id"): + path += "destination_slot_id=" + str(optionals.destination_slot_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, "", "") + + if response.response_code >= 400: + _handle_error("update_guest_star_slot", response) + return response + + +## BETA Allows a caller to remove a slot assignment from a user participating in an active Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## session_id - The ID of the Guest Star session in which to remove the slot assignment. +## guest_id - The Twitch User ID corresponding to the guest to remove from the session. +## slot_id - The slot ID representing the slot assignment to remove from the session. +## +## https://dev.twitch.tv/docs/api/reference#delete-guest-star-slot +func delete_guest_star_slot(opt: TwitchDeleteGuestStarSlot.Opt, guest_id: String, moderator_id: String, session_id: String, slot_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/slot?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "guest_id=" + str(guest_id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "slot_id=" + str(slot_id) + "&" + if optionals.has("should_reinvite_guest"): + path += "should_reinvite_guest=" + str(optionals.should_reinvite_guest) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_guest_star_slot", response) + return response + + +## BETA Allows a user to update slot settings for a particular guest within a Guest Star session. +## +## broadcaster_id - The ID of the broadcaster running the Guest Star session. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## session_id - The ID of the Guest Star session in which to update a slot’s settings. +## slot_id - The slot assignment that has previously been assigned to a user. +## +## https://dev.twitch.tv/docs/api/reference#update-guest-star-slot-settings +func update_guest_star_slot_settings(opt: TwitchUpdateGuestStarSlotSettings.Opt, moderator_id: String, session_id: String, slot_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/guest_star/slot_settings?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + path += "session_id=" + str(session_id) + "&" + path += "slot_id=" + str(slot_id) + "&" + if optionals.has("is_audio_enabled"): + path += "is_audio_enabled=" + str(optionals.is_audio_enabled) + "&" + if optionals.has("is_live"): + path += "is_live=" + str(optionals.is_live) + "&" + if optionals.has("is_video_enabled"): + path += "is_video_enabled=" + str(optionals.is_video_enabled) + "&" + if optionals.has("volume"): + path += "volume=" + str(optionals.volume) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, "", "") + + if response.response_code >= 400: + _handle_error("update_guest_star_slot_settings", response) + return response + + +## DEPRECATED Gets information about the broadcaster’s current or most recent Hype Train event. +## +## broadcaster_id - The ID of the broadcaster that’s running the Hype Train. This ID must match the User ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-hype-train-events +func get_hype_train_events(opt: TwitchGetHypeTrainEvents.Opt, broadcaster_id: String) -> TwitchGetHypeTrainEvents.Response: + var path = "/hypetrain/events?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_hype_train_events", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetHypeTrainEvents.Response = TwitchGetHypeTrainEvents.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetHypeTrainEvents.Opt.new() + opt.after = cursor + parsed_result._next_page = get_hype_train_events.bind(opt, broadcaster_id) + + return parsed_result + + +## NEW Gets the status of a Hype Train for the specified broadcaster. +## +## broadcaster_id - The User ID of the channel broadcaster. +## +## https://dev.twitch.tv/docs/api/reference#get-hype-train-status +func get_hype_train_status(broadcaster_id: String) -> TwitchGetHypeTrainStatus.Response: + var path = "/hypetrain/status?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_hype_train_status", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetHypeTrainStatus.Response = TwitchGetHypeTrainStatus.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Checks whether AutoMod would flag the specified message for review. +## +## broadcaster_id - The ID of the broadcaster whose AutoMod settings and list of blocked terms are used to check the message. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#check-automod-status +func check_automod_status(body: TwitchCheckAutoModStatus.Body, broadcaster_id: String) -> TwitchCheckAutoModStatus.Response: + var path = "/moderation/enforcements/status?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("check_automod_status", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCheckAutoModStatus.Response = TwitchCheckAutoModStatus.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Allow or deny the message that AutoMod flagged for review. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages +func manage_held_automod_messages(body: TwitchManageHeldAutoModMessages.Body) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/automod/message?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + if response.response_code >= 400: + _handle_error("manage_held_automod_messages", response) + return response + + +## Gets the broadcaster’s AutoMod settings. +## +## broadcaster_id - The ID of the broadcaster whose AutoMod settings you want to get. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-automod-settings +func get_automod_settings(moderator_id: String, broadcaster_id: String) -> TwitchGetAutoModSettings.Response: + var path = "/moderation/automod/settings?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_automod_settings", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetAutoModSettings.Response = TwitchGetAutoModSettings.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates the broadcaster’s AutoMod settings. +## +## broadcaster_id - The ID of the broadcaster whose AutoMod settings you want to update. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#update-automod-settings +func update_automod_settings(body: TwitchUpdateAutoModSettings.Body, moderator_id: String, broadcaster_id: String) -> TwitchUpdateAutoModSettings.Response: + var path = "/moderation/automod/settings?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_automod_settings", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateAutoModSettings.Response = TwitchUpdateAutoModSettings.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets all users that the broadcaster banned or put in a timeout. +## +## broadcaster_id - The ID of the broadcaster whose list of banned users you want to get. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-banned-users +func get_banned_users(opt: TwitchGetBannedUsers.Opt, broadcaster_id: String) -> TwitchGetBannedUsers.Response: + var path = "/moderation/banned?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + + for param in optionals.user_id: + path += "user_id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_banned_users", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetBannedUsers.Response = TwitchGetBannedUsers.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetBannedUsers.Opt.new() + opt.after = cursor + parsed_result._next_page = get_banned_users.bind(opt, broadcaster_id) + + return parsed_result + + +## Bans a user from participating in a broadcaster’s chat room or puts them in a timeout. +## +## broadcaster_id - The ID of the broadcaster whose chat room the user is being banned from. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#ban-user +func ban_user(body: TwitchBanUser.Body, moderator_id: String, broadcaster_id: String) -> TwitchBanUser.Response: + var path = "/moderation/bans?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("ban_user", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchBanUser.Response = TwitchBanUser.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Removes the ban or timeout that was placed on the specified user. +## +## broadcaster_id - The ID of the broadcaster whose chat room the user is banned from chatting in. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## user_id - The ID of the user to remove the ban or timeout from. +## +## https://dev.twitch.tv/docs/api/reference#unban-user +func unban_user(moderator_id: String, user_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/bans?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("unban_user", response) + return response + + +## NEW Gets a list of unban requests for a broadcaster’s channel. +## +## broadcaster_id - The ID of the broadcaster whose channel is receiving unban requests. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s unban requests. This ID must match the user ID in the user access token. +## status - Filter by a status. +## +## * pending +## * approved +## * denied +## * acknowledged +## * canceled +## +## https://dev.twitch.tv/docs/api/reference#get-unban-requests +func get_unban_requests(opt: TwitchGetUnbanRequests.Opt, moderator_id: String, status: String, broadcaster_id: String) -> TwitchGetUnbanRequests.Response: + var path = "/moderation/unban_requests?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + path += "status=" + str(status) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_unban_requests", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUnbanRequests.Response = TwitchGetUnbanRequests.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetUnbanRequests.Opt.new() + opt.after = cursor + parsed_result._next_page = get_unban_requests.bind(opt, moderator_id, status, broadcaster_id) + + return parsed_result + + +## NEW Resolves an unban request by approving or denying it. +## +## broadcaster_id - The ID of the broadcaster whose channel is approving or denying the unban request. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s unban requests. This ID must match the user ID in the user access token. +## unban_request_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s unban requests. This ID must match the user ID in the user access token. +## status - Resolution status. +## +## * approved +## * denied +## +## https://dev.twitch.tv/docs/api/reference#resolve-unban-requests +func resolve_unban_requests(opt: TwitchResolveUnbanRequests.Opt, moderator_id: String, status: String, unban_request_id: String, broadcaster_id: String) -> TwitchResolveUnbanRequests.Response: + var path = "/moderation/unban_requests?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + path += "status=" + str(status) + "&" + path += "unban_request_id=" + str(unban_request_id) + "&" + if optionals.has("resolution_text"): + path += "resolution_text=" + str(optionals.resolution_text) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("resolve_unban_requests", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchResolveUnbanRequests.Response = TwitchResolveUnbanRequests.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the broadcaster’s list of non-private, blocked words or phrases. +## +## broadcaster_id - The ID of the broadcaster whose blocked terms you’re getting. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-blocked-terms +func get_blocked_terms(opt: TwitchGetBlockedTerms.Opt, moderator_id: String, broadcaster_id: String) -> TwitchGetBlockedTerms.Response: + var path = "/moderation/blocked_terms?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_blocked_terms", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetBlockedTerms.Response = TwitchGetBlockedTerms.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetBlockedTerms.Opt.new() + opt.after = cursor + parsed_result._next_page = get_blocked_terms.bind(opt, moderator_id, broadcaster_id) + + return parsed_result + + +## Adds a word or phrase to the broadcaster’s list of blocked terms. +## +## broadcaster_id - The ID of the broadcaster that owns the list of blocked terms. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#add-blocked-term +func add_blocked_term(body: TwitchAddBlockedTerm.Body, moderator_id: String, broadcaster_id: String) -> TwitchAddBlockedTerm.Response: + var path = "/moderation/blocked_terms?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("add_blocked_term", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchAddBlockedTerm.Response = TwitchAddBlockedTerm.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Removes the word or phrase from the broadcaster’s list of blocked terms. +## +## broadcaster_id - The ID of the broadcaster that owns the list of blocked terms. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## id - The ID of the blocked term to remove from the broadcaster’s list of blocked terms. +## +## https://dev.twitch.tv/docs/api/reference#remove-blocked-term +func remove_blocked_term(id: String, moderator_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/blocked_terms?" + path += "id=" + str(id) + "&" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("remove_blocked_term", response) + return response + + +## Removes a single chat message or all chat messages from the broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster that owns the chat room to remove messages from. +## moderator_id - The ID of the broadcaster or a user that has permission to moderate the broadcaster’s chat room. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#delete-chat-messages +func delete_chat_messages(opt: TwitchDeleteChatMessages.Opt, moderator_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/chat?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "moderator_id=" + str(moderator_id) + "&" + if optionals.has("message_id"): + path += "message_id=" + str(optionals.message_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_chat_messages", response) + return response + + +## Gets a list of channels that the specified user has moderator privileges in. +## +## user_id - A user’s ID. Returns the list of channels that this user has moderator privileges in. This ID must match the user ID in the user OAuth token +## +## https://dev.twitch.tv/docs/api/reference#get-moderated-channels +func get_moderated_channels(opt: TwitchGetModeratedChannels.Opt, user_id: String) -> TwitchGetModeratedChannels.Response: + var path = "/moderation/channels?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "user_id=" + str(user_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_moderated_channels", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetModeratedChannels.Response = TwitchGetModeratedChannels.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetModeratedChannels.Opt.new() + opt.after = cursor + parsed_result._next_page = get_moderated_channels.bind(opt, user_id) + + return parsed_result + + +## Gets all users allowed to moderate the broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster whose list of moderators you want to get. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-moderators +func get_moderators(opt: TwitchGetModerators.Opt, broadcaster_id: String) -> TwitchGetModerators.Response: + var path = "/moderation/moderators?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + + for param in optionals.user_id: + path += "user_id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_moderators", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetModerators.Response = TwitchGetModerators.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetModerators.Opt.new() + opt.after = cursor + parsed_result._next_page = get_moderators.bind(opt, broadcaster_id) + + return parsed_result + + +## Adds a moderator to the broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster that owns the chat room. This ID must match the user ID in the access token. +## user_id - The ID of the user to add as a moderator in the broadcaster’s chat room. +## +## https://dev.twitch.tv/docs/api/reference#add-channel-moderator +func add_channel_moderator(user_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/moderators?" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + if response.response_code >= 400: + _handle_error("add_channel_moderator", response) + return response + + +## Removes a moderator from the broadcaster’s chat room. +## +## broadcaster_id - The ID of the broadcaster that owns the chat room. This ID must match the user ID in the access token. +## user_id - The ID of the user to remove as a moderator from the broadcaster’s chat room. +## +## https://dev.twitch.tv/docs/api/reference#remove-channel-moderator +func remove_channel_moderator(user_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/moderation/moderators?" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("remove_channel_moderator", response) + return response + + +## Gets a list of the broadcaster’s VIPs. +## +## broadcaster_id - The ID of the broadcaster whose list of VIPs you want to get. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-vips +func get_vips(opt: TwitchGetVips.Opt, broadcaster_id: String) -> TwitchGetVIPs.Response: + var path = "/channels/vips?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + + for param in optionals.user_id: + path += "user_id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_vips", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetVIPs.Response = TwitchGetVIPs.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetVips.Opt.new() + opt.after = cursor + parsed_result._next_page = get_vips.bind(opt, broadcaster_id) + + return parsed_result + + +## Adds the specified user as a VIP in the broadcaster’s channel. +## +## user_id - The ID of the user to give VIP status to. +## broadcaster_id - The ID of the broadcaster that’s adding the user as a VIP. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#add-channel-vip +func add_channel_vip(user_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/channels/vips?" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + if response.response_code >= 400: + _handle_error("add_channel_vip", response) + return response + + +## Removes the specified user as a VIP in the broadcaster’s channel. +## +## user_id - The ID of the user to remove VIP status from. +## broadcaster_id - The ID of the broadcaster who owns the channel where the user has VIP status. +## +## https://dev.twitch.tv/docs/api/reference#remove-channel-vip +func remove_channel_vip(user_id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/channels/vips?" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("remove_channel_vip", response) + return response + + +## Activates or deactivates the broadcaster’s Shield Mode. +## +## broadcaster_id - The ID of the broadcaster whose Shield Mode you want to activate or deactivate. +## moderator_id - The ID of the broadcaster or a user that is one of the broadcaster’s moderators. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#update-shield-mode-status +func update_shield_mode_status(body: TwitchUpdateShieldModeStatus.Body, moderator_id: String, broadcaster_id: String) -> TwitchUpdateShieldModeStatus.Response: + var path = "/moderation/shield_mode?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_shield_mode_status", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateShieldModeStatus.Response = TwitchUpdateShieldModeStatus.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the broadcaster’s Shield Mode activation status. +## +## broadcaster_id - The ID of the broadcaster whose Shield Mode activation status you want to get. +## moderator_id - The ID of the broadcaster or a user that is one of the broadcaster’s moderators. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-shield-mode-status +func get_shield_mode_status(moderator_id: String, broadcaster_id: String) -> TwitchGetShieldModeStatus.Response: + var path = "/moderation/shield_mode?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_shield_mode_status", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetShieldModeStatus.Response = TwitchGetShieldModeStatus.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## NEW Warns a user in the specified broadcaster’s chat room, preventing them from chat interaction until the warning is acknowledged. +## +## broadcaster_id - The ID of the channel in which the warning will take effect. +## moderator_id - The ID of the twitch user who requested the warning. +## +## https://dev.twitch.tv/docs/api/reference#warn-chat-user +func warn_chat_user(body: TwitchWarnChatUser.Body, moderator_id: String, broadcaster_id: String) -> TwitchWarnChatUser.Response: + var path = "/moderation/warnings?" + path += "moderator_id=" + str(moderator_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("warn_chat_user", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchWarnChatUser.Response = TwitchWarnChatUser.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of polls that the broadcaster created. +## +## broadcaster_id - The ID of the broadcaster that created the polls. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-polls +func get_polls(opt: TwitchGetPolls.Opt, broadcaster_id: String) -> TwitchGetPolls.Response: + var path = "/polls?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_polls", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetPolls.Response = TwitchGetPolls.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetPolls.Opt.new() + opt.after = cursor + parsed_result._next_page = get_polls.bind(opt, broadcaster_id) + + return parsed_result + + +## Creates a poll that viewers in the broadcaster’s channel can vote on. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#create-poll +func create_poll(body: TwitchCreatePoll.Body) -> TwitchCreatePoll.Response: + var path = "/polls?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_poll", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreatePoll.Response = TwitchCreatePoll.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## End an active poll. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#end-poll +func end_poll(body: TwitchEndPoll.Body) -> TwitchEndPoll.Response: + var path = "/polls?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("end_poll", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchEndPoll.Response = TwitchEndPoll.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of Channel Points Predictions that the broadcaster created. +## +## broadcaster_id - The ID of the broadcaster whose predictions you want to get. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#get-predictions +func get_predictions(opt: TwitchGetPredictions.Opt, broadcaster_id: String) -> TwitchGetPredictions.Response: + var path = "/predictions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_predictions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetPredictions.Response = TwitchGetPredictions.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetPredictions.Opt.new() + opt.after = cursor + parsed_result._next_page = get_predictions.bind(opt, broadcaster_id) + + return parsed_result + + +## Create a Channel Points Prediction. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#create-prediction +func create_prediction(body: TwitchCreatePrediction.Body) -> TwitchCreatePrediction.Response: + var path = "/predictions?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_prediction", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreatePrediction.Response = TwitchCreatePrediction.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Locks, resolves, or cancels a Channel Points Prediction. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#end-prediction +func end_prediction(body: TwitchEndPrediction.Body) -> TwitchEndPrediction.Response: + var path = "/predictions?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("end_prediction", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchEndPrediction.Response = TwitchEndPrediction.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Raid another channel by sending the broadcaster’s viewers to the targeted channel. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#start-a-raid +func start_a_raid(opt: TwitchStartARaid.Opt) -> TwitchStartRaid.Response: + var path = "/raids?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("from_broadcaster_id"): + path += "from_broadcaster_id=" + str(optionals.from_broadcaster_id) + "&" + if optionals.has("to_broadcaster_id"): + path += "to_broadcaster_id=" + str(optionals.to_broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("start_a_raid", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchStartRaid.Response = TwitchStartRaid.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Cancel a pending raid. +## +## broadcaster_id - The ID of the broadcaster that initiated the raid. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#cancel-a-raid +func cancel_a_raid(broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/raids?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("cancel_a_raid", response) + return response + + +## Gets the broadcaster’s streaming schedule. +## +## broadcaster_id - The ID of the broadcaster that owns the streaming schedule you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-stream-schedule +func get_channel_stream_schedule(opt: TwitchGetChannelStreamSchedule.Opt, broadcaster_id: String) -> TwitchGetChannelStreamSchedule.Response: + var path = "/schedule?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("start_time"): + path += "start_time=" + get_rfc_3339_date_format(optionals.start_time) + "&" + if optionals.has("utc_offset"): + path += "utc_offset=" + str(optionals.utc_offset) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_stream_schedule", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelStreamSchedule.Response = TwitchGetChannelStreamSchedule.Response.from_json(result) + parsed_result.response = response + + if parsed_result.data.pagination != null: + opt.after = parsed_result.data.pagination.cursor + parsed_result.data._next_page = get_channel_stream_schedule.bind(opt, broadcaster_id) + + return parsed_result + + +## Gets the broadcaster’s streaming schedule as an iCalendar. +## +## broadcaster_id - The ID of the broadcaster that owns the streaming schedule you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-icalendar +func get_channel_icalendar(broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/schedule/icalendar?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + if response.response_code >= 400: + _handle_error("get_channel_icalendar", response) + return response + + +## Updates the broadcaster’s schedule settings, such as scheduling a vacation. +## +## broadcaster_id - The ID of the broadcaster whose schedule settings you want to update. The ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#update-channel-stream-schedule +func update_channel_stream_schedule(opt: TwitchUpdateChannelStreamSchedule.Opt, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/schedule/settings?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("is_vacation_enabled"): + path += "is_vacation_enabled=" + str(optionals.is_vacation_enabled) + "&" + if optionals.has("timezone"): + path += "timezone=" + str(optionals.timezone) + "&" + if optionals.has("vacation_end_time"): + path += "vacation_end_time=" + get_rfc_3339_date_format(optionals.vacation_end_time) + "&" + if optionals.has("vacation_start_time"): + path += "vacation_start_time=" + get_rfc_3339_date_format(optionals.vacation_start_time) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, "", "") + + if response.response_code >= 400: + _handle_error("update_channel_stream_schedule", response) + return response + + +## Adds a single or recurring broadcast to the broadcaster’s streaming schedule. +## +## broadcaster_id - The ID of the broadcaster that owns the schedule to add the broadcast segment to. This ID must match the user ID in the user access token. +## +## https://dev.twitch.tv/docs/api/reference#create-channel-stream-schedule-segment +func create_channel_stream_schedule_segment(body: TwitchCreateChannelStreamScheduleSegment.Body, broadcaster_id: String) -> TwitchCreateChannelStreamScheduleSegment.Response: + var path = "/schedule/segment?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_channel_stream_schedule_segment", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateChannelStreamScheduleSegment.Response = TwitchCreateChannelStreamScheduleSegment.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates a scheduled broadcast segment. +## +## broadcaster_id - The ID of the broadcaster who owns the broadcast segment to update. This ID must match the user ID in the user access token. +## id - The ID of the broadcast segment to update. +## +## https://dev.twitch.tv/docs/api/reference#update-channel-stream-schedule-segment +func update_channel_stream_schedule_segment(body: TwitchUpdateChannelStreamScheduleSegment.Body, id: String, broadcaster_id: String) -> TwitchUpdateChannelStreamScheduleSegment.Response: + var path = "/schedule/segment?" + path += "id=" + str(id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PATCH, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_channel_stream_schedule_segment", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateChannelStreamScheduleSegment.Response = TwitchUpdateChannelStreamScheduleSegment.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Deletes a broadcast from the broadcaster’s streaming schedule. +## +## broadcaster_id - The ID of the broadcaster that owns the streaming schedule. This ID must match the user ID in the user access token. +## id - The ID of the broadcast segment to remove. +## +## https://dev.twitch.tv/docs/api/reference#delete-channel-stream-schedule-segment +func delete_channel_stream_schedule_segment(id: String, broadcaster_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/schedule/segment?" + path += "id=" + str(id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("delete_channel_stream_schedule_segment", response) + return response + + +## Gets the games or categories that match the specified query. +## +## query - The URI-encoded search string. For example, encode _#archery_ as `%23archery` and search strings like _angel of death_ as `angel%20of%20death`. +## +## https://dev.twitch.tv/docs/api/reference#search-categories +func search_categories(opt: TwitchSearchCategories.Opt, query: String) -> TwitchSearchCategories.Response: + var path = "/search/categories?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "query=" + str(query) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("search_categories", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchSearchCategories.Response = TwitchSearchCategories.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchSearchCategories.Opt.new() + opt.after = cursor + parsed_result._next_page = search_categories.bind(opt, query) + + return parsed_result + + +## Gets the channels that match the specified query and have streamed content within the past 6 months. +## +## query - The URI-encoded search string. For example, encode search strings like _angel of death_ as `angel%20of%20death`. +## +## https://dev.twitch.tv/docs/api/reference#search-channels +func search_channels(opt: TwitchSearchChannels.Opt, query: String) -> TwitchSearchChannels.Response: + var path = "/search/channels?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "query=" + str(query) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("live_only"): + path += "live_only=" + str(optionals.live_only) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("search_channels", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchSearchChannels.Response = TwitchSearchChannels.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchSearchChannels.Opt.new() + opt.after = cursor + parsed_result._next_page = search_channels.bind(opt, query) + + return parsed_result + + +## Gets the channel’s stream key. +## +## broadcaster_id - The ID of the broadcaster that owns the channel. The ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-stream-key +func get_stream_key(broadcaster_id: String) -> TwitchGetStreamKey.Response: + var path = "/streams/key?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_stream_key", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetStreamKey.Response = TwitchGetStreamKey.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of all streams. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-streams +func get_streams(opt: TwitchGetStreams.Opt) -> TwitchGetStreams.Response: + var path = "/streams?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("game_id"): + + for param in optionals.game_id: + path += "game_id=" + str(param) + "&" + if optionals.has("language"): + + for param in optionals.language: + path += "language=" + str(param) + "&" + if optionals.has("type"): + path += "type=" + str(optionals.type) + "&" + if optionals.has("user_id"): + + for param in optionals.user_id: + path += "user_id=" + str(param) + "&" + if optionals.has("user_login"): + + for param in optionals.user_login: + path += "user_login=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_streams", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetStreams.Response = TwitchGetStreams.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetStreams.Opt.new() + opt.after = cursor + parsed_result._next_page = get_streams.bind(opt) + + return parsed_result + + +## Gets the list of broadcasters that the user follows and that are streaming live. +## +## user_id - The ID of the user whose list of followed streams you want to get. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-followed-streams +func get_followed_streams(opt: TwitchGetFollowedStreams.Opt, user_id: String) -> TwitchGetFollowedStreams.Response: + var path = "/streams/followed?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "user_id=" + str(user_id) + "&" + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_followed_streams", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetFollowedStreams.Response = TwitchGetFollowedStreams.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetFollowedStreams.Opt.new() + opt.after = cursor + parsed_result._next_page = get_followed_streams.bind(opt, user_id) + + return parsed_result + + +## Adds a marker to a live stream. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#create-stream-marker +func create_stream_marker(body: TwitchCreateStreamMarker.Body) -> TwitchCreateStreamMarker.Response: + var path = "/streams/markers?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("create_stream_marker", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCreateStreamMarker.Response = TwitchCreateStreamMarker.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets a list of markers from the user’s most recent stream or from the specified VOD/video. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-stream-markers +func get_stream_markers(opt: TwitchGetStreamMarkers.Opt) -> TwitchGetStreamMarkers.Response: + var path = "/streams/markers?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + if optionals.has("video_id"): + path += "video_id=" + str(optionals.video_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_stream_markers", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetStreamMarkers.Response = TwitchGetStreamMarkers.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetStreamMarkers.Opt.new() + opt.after = cursor + parsed_result._next_page = get_stream_markers.bind(opt) + + return parsed_result + + +## Gets a list of users that subscribe to the specified broadcaster. +## +## broadcaster_id - The broadcaster’s ID. This ID must match the user ID in the access token. +## +## https://dev.twitch.tv/docs/api/reference#get-broadcaster-subscriptions +func get_broadcaster_subscriptions(opt: TwitchGetBroadcasterSubscriptions.Opt, broadcaster_id: String) -> TwitchGetBroadcasterSubscriptions.Response: + var path = "/subscriptions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("user_id"): + + for param in optionals.user_id: + path += "user_id=" + str(param) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_broadcaster_subscriptions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetBroadcasterSubscriptions.Response = TwitchGetBroadcasterSubscriptions.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetBroadcasterSubscriptions.Opt.new() + opt.after = cursor + parsed_result._next_page = get_broadcaster_subscriptions.bind(opt, broadcaster_id) + + return parsed_result + + +## Checks whether the user subscribes to the broadcaster’s channel. +## +## broadcaster_id - The ID of a partner or affiliate broadcaster. +## user_id - The ID of the user that you’re checking to see whether they subscribe to the broadcaster in _broadcaster\_id_. This ID must match the user ID in the access Token. +## +## https://dev.twitch.tv/docs/api/reference#check-user-subscription +func check_user_subscription(user_id: String, broadcaster_id: String) -> TwitchCheckUserSubscription.Response: + var path = "/subscriptions/user?" + path += "user_id=" + str(user_id) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("check_user_subscription", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchCheckUserSubscription.Response = TwitchCheckUserSubscription.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the list of all stream tags that Twitch defines. You can also filter the list by one or more tag IDs. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-all-stream-tags +func get_all_stream_tags(opt: TwitchGetAllStreamTags.Opt) -> TwitchGetAllStreamTags.Response: + var path = "/tags/streams?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("tag_id"): + + for param in optionals.tag_id: + path += "tag_id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_all_stream_tags", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetAllStreamTags.Response = TwitchGetAllStreamTags.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetAllStreamTags.Opt.new() + opt.after = cursor + parsed_result._next_page = get_all_stream_tags.bind(opt) + + return parsed_result + + +## Gets the list of stream tags that the broadcaster or Twitch added to their channel. +## +## broadcaster_id - The ID of the broadcaster whose stream tags you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-stream-tags +func get_stream_tags(broadcaster_id: String) -> TwitchGetStreamTags.Response: + var path = "/streams/tags?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_stream_tags", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetStreamTags.Response = TwitchGetStreamTags.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the list of Twitch teams that the broadcaster is a member of. +## +## broadcaster_id - The ID of the broadcaster whose teams you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-channel-teams +func get_channel_teams(broadcaster_id: String) -> TwitchGetChannelTeams.Response: + var path = "/teams/channel?" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_channel_teams", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetChannelTeams.Response = TwitchGetChannelTeams.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about the specified Twitch team. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-teams +func get_teams(opt: TwitchGetTeams.Opt) -> TwitchGetTeams.Response: + var path = "/teams?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("id"): + path += "id=" + str(optionals.id) + "&" + if optionals.has("name"): + path += "name=" + str(optionals.name) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_teams", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetTeams.Response = TwitchGetTeams.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about one or more users. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-users +func get_users(opt: TwitchGetUsers.Opt) -> TwitchGetUsers.Response: + var path = "/users?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("login"): + + for param in optionals.login: + path += "login=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_users", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUsers.Response = TwitchGetUsers.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates the user’s information. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-user +func update_user(opt: TwitchUpdateUser.Opt) -> TwitchUpdateUser.Response: + var path = "/users?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("description"): + path += "description=" + str(optionals.description) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_user", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateUser.Response = TwitchUpdateUser.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the list of users that the broadcaster has blocked. +## +## broadcaster_id - The ID of the broadcaster whose list of blocked users you want to get. +## +## https://dev.twitch.tv/docs/api/reference#get-user-block-list +func get_user_block_list(opt: TwitchGetUserBlockList.Opt, broadcaster_id: String) -> TwitchGetUserBlockList.Response: + var path = "/users/blocks?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + path += "broadcaster_id=" + str(broadcaster_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_user_block_list", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUserBlockList.Response = TwitchGetUserBlockList.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetUserBlockList.Opt.new() + opt.after = cursor + parsed_result._next_page = get_user_block_list.bind(opt, broadcaster_id) + + return parsed_result + + +## Blocks the specified user from interacting with or having contact with the broadcaster. +## +## target_user_id - The ID of the user to block. The API ignores the request if the broadcaster has already blocked the user. +## +## https://dev.twitch.tv/docs/api/reference#block-user +func block_user(opt: TwitchBlockUser.Opt, target_user_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/users/blocks?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + path += "target_user_id=" + str(target_user_id) + "&" + if optionals.has("reason"): + path += "reason=" + str(optionals.reason) + "&" + if optionals.has("source_context"): + path += "source_context=" + str(optionals.source_context) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, "", "") + + if response.response_code >= 400: + _handle_error("block_user", response) + return response + + +## Removes the user from the broadcaster’s list of blocked users. +## +## target_user_id - The ID of the user to remove from the broadcaster’s list of blocked users. The API ignores the request if the broadcaster hasn’t blocked the user. +## +## https://dev.twitch.tv/docs/api/reference#unblock-user +func unblock_user(target_user_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/users/blocks?" + path += "target_user_id=" + str(target_user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + if response.response_code >= 400: + _handle_error("unblock_user", response) + return response + + +## Gets a list of all extensions (both active and inactive) that the broadcaster has installed. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-user-extensions +func get_user_extensions() -> TwitchGetUserExtensions.Response: + var path = "/users/extensions/list?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_user_extensions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUserExtensions.Response = TwitchGetUserExtensions.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets the active extensions that the broadcaster has installed for each configuration. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-user-active-extensions +func get_user_active_extensions(opt: TwitchGetUserActiveExtensions.Opt) -> TwitchGetUserActiveExtensions.Response: + var path = "/users/extensions?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_user_active_extensions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetUserActiveExtensions.Response = TwitchGetUserActiveExtensions.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Updates an installed extension’s information. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#update-user-extensions +func update_user_extensions(body: TwitchUpdateUserExtensions.Body) -> TwitchUpdateUserExtensions.Response: + var path = "/users/extensions?" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_PUT, body, "application/json") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("update_user_extensions", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchUpdateUserExtensions.Response = TwitchUpdateUserExtensions.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Gets information about one or more published videos. +## +## [no required query parameters to describe] +## +## https://dev.twitch.tv/docs/api/reference#get-videos +func get_videos(opt: TwitchGetVideos.Opt) -> TwitchGetVideos.Response: + var path = "/videos?" + var optionals: Dictionary[StringName, Variant] = {} + if opt != null: optionals = opt.to_dict() + if optionals.has("after"): + path += "after=" + str(optionals.after) + "&" + if optionals.has("before"): + path += "before=" + str(optionals.before) + "&" + if optionals.has("first"): + path += "first=" + str(optionals.first) + "&" + if optionals.has("game_id"): + path += "game_id=" + str(optionals.game_id) + "&" + if optionals.has("id"): + + for param in optionals.id: + path += "id=" + str(param) + "&" + if optionals.has("language"): + path += "language=" + str(optionals.language) + "&" + if optionals.has("period"): + path += "period=" + str(optionals.period) + "&" + if optionals.has("sort"): + path += "sort=" + str(optionals.sort) + "&" + if optionals.has("type"): + path += "type=" + str(optionals.type) + "&" + if optionals.has("user_id"): + path += "user_id=" + str(optionals.user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_GET, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("get_videos", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchGetVideos.Response = TwitchGetVideos.Response.from_json(result) + parsed_result.response = response + if parsed_result.pagination != null: + var cursor: String = parsed_result.pagination.cursor + if not opt: opt = TwitchGetVideos.Opt.new() + opt.after = cursor + parsed_result._next_page = get_videos.bind(opt) + + return parsed_result + + +## Deletes one or more videos. +## +## id - The list of videos to delete. To specify more than one video, include the _id_ parameter for each video to delete. For example, `id=1234&id=5678`. You can delete a maximum of 5 videos per request. Ignores invalid video IDs. +## +## If the user doesn’t have permission to delete one of the videos in the list, none of the videos are deleted. +## +## https://dev.twitch.tv/docs/api/reference#delete-videos +func delete_videos(id: Array[String]) -> TwitchDeleteVideos.Response: + var path = "/videos?" + + for param in id: + path += "id=" + str(param) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_DELETE, "", "") + + var result: Variant = {} + if response.response_code >= 400: + _handle_error("delete_videos", response) + else: + result = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var parsed_result: TwitchDeleteVideos.Response = TwitchDeleteVideos.Response.from_json(result) + parsed_result.response = response + + return parsed_result + + +## Sends a whisper message to the specified user. +## +## from_user_id - The ID of the user sending the whisper. This user must have a verified phone number. This ID must match the user ID in the user access token. +## to_user_id - The ID of the user to receive the whisper. +## +## https://dev.twitch.tv/docs/api/reference#send-whisper +func send_whisper(body: TwitchSendWhisper.Body, from_user_id: String, to_user_id: String) -> BufferedHTTPClient.ResponseData: + var path = "/whispers?" + path += "from_user_id=" + str(from_user_id) + "&" + path += "to_user_id=" + str(to_user_id) + "&" + + var response: BufferedHTTPClient.ResponseData = await request(path, HTTPClient.METHOD_POST, body, "application/json") + + if response.response_code >= 400: + _handle_error("send_whisper", response) + return response diff --git a/addons/twitcher/generated/twitch_api.gd.uid b/addons/twitcher/generated/twitch_api.gd.uid new file mode 100644 index 00000000..92d40dc5 --- /dev/null +++ b/addons/twitcher/generated/twitch_api.gd.uid @@ -0,0 +1 @@ +uid://cw30cwveway65 diff --git a/addons/twitcher/generated/twitch_auto_mod_settings.gd b/addons/twitcher/generated/twitch_auto_mod_settings.gd new file mode 100644 index 00000000..0394af82 --- /dev/null +++ b/addons/twitcher/generated/twitch_auto_mod_settings.gd @@ -0,0 +1,119 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/AutoModSettings +class_name TwitchAutoModSettings + +## The broadcaster’s ID. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The moderator’s ID. +@export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + +## The default AutoMod level for the broadcaster. This field is **null** if the broadcaster has set one or more of the individual settings. +@export var overall_level: int: + set(val): + overall_level = val + track_data(&"overall_level", val) + +## The Automod level for discrimination against disability. +@export var disability: int: + set(val): + disability = val + track_data(&"disability", val) + +## The Automod level for hostility involving aggression. +@export var aggression: int: + set(val): + aggression = val + track_data(&"aggression", val) + +## The AutoMod level for discrimination based on sexuality, sex, or gender. +@export var sexuality_sex_or_gender: int: + set(val): + sexuality_sex_or_gender = val + track_data(&"sexuality_sex_or_gender", val) + +## The Automod level for discrimination against women. +@export var misogyny: int: + set(val): + misogyny = val + track_data(&"misogyny", val) + +## The Automod level for hostility involving name calling or insults. +@export var bullying: int: + set(val): + bullying = val + track_data(&"bullying", val) + +## The Automod level for profanity. +@export var swearing: int: + set(val): + swearing = val + track_data(&"swearing", val) + +## The Automod level for racial discrimination. +@export var race_ethnicity_or_religion: int: + set(val): + race_ethnicity_or_religion = val + track_data(&"race_ethnicity_or_religion", val) + +## The Automod level for sexual content. +@export var sex_based_terms: int: + set(val): + sex_based_terms = val + track_data(&"sex_based_terms", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _moderator_id: String, _overall_level: int, _disability: int, _aggression: int, _sexuality_sex_or_gender: int, _misogyny: int, _bullying: int, _swearing: int, _race_ethnicity_or_religion: int, _sex_based_terms: int) -> TwitchAutoModSettings: + var twitch_auto_mod_settings: TwitchAutoModSettings = TwitchAutoModSettings.new() + twitch_auto_mod_settings.broadcaster_id = _broadcaster_id + twitch_auto_mod_settings.moderator_id = _moderator_id + twitch_auto_mod_settings.overall_level = _overall_level + twitch_auto_mod_settings.disability = _disability + twitch_auto_mod_settings.aggression = _aggression + twitch_auto_mod_settings.sexuality_sex_or_gender = _sexuality_sex_or_gender + twitch_auto_mod_settings.misogyny = _misogyny + twitch_auto_mod_settings.bullying = _bullying + twitch_auto_mod_settings.swearing = _swearing + twitch_auto_mod_settings.race_ethnicity_or_religion = _race_ethnicity_or_religion + twitch_auto_mod_settings.sex_based_terms = _sex_based_terms + return twitch_auto_mod_settings + + +static func from_json(d: Dictionary) -> TwitchAutoModSettings: + var result: TwitchAutoModSettings = TwitchAutoModSettings.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("overall_level", null) != null: + result.overall_level = d["overall_level"] + if d.get("disability", null) != null: + result.disability = d["disability"] + if d.get("aggression", null) != null: + result.aggression = d["aggression"] + if d.get("sexuality_sex_or_gender", null) != null: + result.sexuality_sex_or_gender = d["sexuality_sex_or_gender"] + if d.get("misogyny", null) != null: + result.misogyny = d["misogyny"] + if d.get("bullying", null) != null: + result.bullying = d["bullying"] + if d.get("swearing", null) != null: + result.swearing = d["swearing"] + if d.get("race_ethnicity_or_religion", null) != null: + result.race_ethnicity_or_religion = d["race_ethnicity_or_religion"] + if d.get("sex_based_terms", null) != null: + result.sex_based_terms = d["sex_based_terms"] + return result diff --git a/addons/twitcher/generated/twitch_auto_mod_settings.gd.uid b/addons/twitcher/generated/twitch_auto_mod_settings.gd.uid new file mode 100644 index 00000000..5414b732 --- /dev/null +++ b/addons/twitcher/generated/twitch_auto_mod_settings.gd.uid @@ -0,0 +1 @@ +uid://da1xnqcvu7tc0 diff --git a/addons/twitcher/generated/twitch_auto_mod_status.gd b/addons/twitcher/generated/twitch_auto_mod_status.gd new file mode 100644 index 00000000..a08c03ed --- /dev/null +++ b/addons/twitcher/generated/twitch_auto_mod_status.gd @@ -0,0 +1,38 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/AutoModStatus +class_name TwitchAutoModStatus + +## The caller-defined ID passed in the request. +@export var msg_id: String: + set(val): + msg_id = val + track_data(&"msg_id", val) + +## A Boolean value that indicates whether Twitch would approve the message for chat or hold it for moderator review or block it from chat. Is **true** if Twitch would approve the message; otherwise, **false** if Twitch would hold the message for moderator review or block it from chat. +@export var is_permitted: bool: + set(val): + is_permitted = val + track_data(&"is_permitted", val) + + + +## Constructor with all required fields. +static func create(_msg_id: String, _is_permitted: bool) -> TwitchAutoModStatus: + var twitch_auto_mod_status: TwitchAutoModStatus = TwitchAutoModStatus.new() + twitch_auto_mod_status.msg_id = _msg_id + twitch_auto_mod_status.is_permitted = _is_permitted + return twitch_auto_mod_status + + +static func from_json(d: Dictionary) -> TwitchAutoModStatus: + var result: TwitchAutoModStatus = TwitchAutoModStatus.new() + if d.get("msg_id", null) != null: + result.msg_id = d["msg_id"] + if d.get("is_permitted", null) != null: + result.is_permitted = d["is_permitted"] + return result diff --git a/addons/twitcher/generated/twitch_auto_mod_status.gd.uid b/addons/twitcher/generated/twitch_auto_mod_status.gd.uid new file mode 100644 index 00000000..a3021eb6 --- /dev/null +++ b/addons/twitcher/generated/twitch_auto_mod_status.gd.uid @@ -0,0 +1 @@ +uid://ysgxjcmt1nul diff --git a/addons/twitcher/generated/twitch_ban_user.gd b/addons/twitcher/generated/twitch_ban_user.gd new file mode 100644 index 00000000..e9d4c734 --- /dev/null +++ b/addons/twitcher/generated/twitch_ban_user.gd @@ -0,0 +1,172 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchBanUser + + + +## +## #/components/schemas/BanUserBody +class Body extends TwitchData: + + ## Identifies the user and type of ban. + @export var data: BodyData: + set(val): + data = val + track_data(&"data", val) + + + + ## Constructor with all required fields. + static func create(_data: BodyData) -> Body: + var body: Body = Body.new() + body.data = _data + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("data", null) != null: + result.data = BodyData.from_json(d["data"]) + return result + + + +## Identifies the user and type of ban. +## #/components/schemas/BanUserBody/Data +class BodyData extends TwitchData: + + ## The ID of the user to ban or put in a timeout. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## To ban a user indefinitely, don’t include this field. + ## + ## To put a user in a timeout, include this field and specify the timeout period, in seconds. The minimum timeout is 1 second and the maximum is 1,209,600 seconds (2 weeks). + ## + ## To end a user’s timeout early, set this field to 1, or use the [Unban user](https://dev.twitch.tv/docs/api/reference#unban-user) endpoint. + @export var duration: int: + set(val): + duration = val + track_data(&"duration", val) + + ## The reason the you’re banning the user or putting them in a timeout. The text is user defined and is limited to a maximum of 500 characters. + @export var reason: String: + set(val): + reason = val + track_data(&"reason", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String) -> BodyData: + var body_data: BodyData = BodyData.new() + body_data.user_id = _user_id + return body_data + + + static func from_json(d: Dictionary) -> BodyData: + var result: BodyData = BodyData.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("reason", null) != null: + result.reason = d["reason"] + return result + + + +## +## #/components/schemas/BanUserResponse +class Response extends TwitchData: + + ## A list that contains the user you successfully banned or put in a timeout. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains the user you successfully banned or put in a timeout. +## #/components/schemas/BanUserResponse/Data +class ResponseData extends TwitchData: + + ## The broadcaster whose chat room the user was banned from chatting in. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The moderator that banned or put the user in the timeout. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The user that was banned or put in a timeout. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The UTC date and time (in RFC3339 format) that the ban or timeout was placed. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## The UTC date and time (in RFC3339 format) that the timeout will end. Is **null** if the user was banned instead of being put in a timeout. + @export var end_time: String: + set(val): + end_time = val + track_data(&"end_time", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _moderator_id: String, _user_id: String, _created_at: String, _end_time: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.broadcaster_id = _broadcaster_id + response_data.moderator_id = _moderator_id + response_data.user_id = _user_id + response_data.created_at = _created_at + response_data.end_time = _end_time + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("end_time", null) != null: + result.end_time = d["end_time"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_ban_user.gd.uid b/addons/twitcher/generated/twitch_ban_user.gd.uid new file mode 100644 index 00000000..88580089 --- /dev/null +++ b/addons/twitcher/generated/twitch_ban_user.gd.uid @@ -0,0 +1 @@ +uid://by54cigjuq85x diff --git a/addons/twitcher/generated/twitch_banned_user.gd b/addons/twitcher/generated/twitch_banned_user.gd new file mode 100644 index 00000000..4515c471 --- /dev/null +++ b/addons/twitcher/generated/twitch_banned_user.gd @@ -0,0 +1,101 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/BannedUser +class_name TwitchBannedUser + +## The ID of the banned user. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The banned user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The banned user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The UTC date and time (in RFC3339 format) of when the timeout expires, or an empty string if the user is permanently banned. +@export var expires_at: String: + set(val): + expires_at = val + track_data(&"expires_at", val) + +## The UTC date and time (in RFC3339 format) of when the user was banned. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The reason the user was banned or put in a timeout if the moderator provided one. +@export var reason: String: + set(val): + reason = val + track_data(&"reason", val) + +## The ID of the moderator that banned the user or put them in a timeout. +@export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + +## The moderator’s login name. +@export var moderator_login: String: + set(val): + moderator_login = val + track_data(&"moderator_login", val) + +## The moderator’s display name. +@export var moderator_name: String: + set(val): + moderator_name = val + track_data(&"moderator_name", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _user_name: String, _expires_at: String, _created_at: String, _reason: String, _moderator_id: String, _moderator_login: String, _moderator_name: String) -> TwitchBannedUser: + var twitch_banned_user: TwitchBannedUser = TwitchBannedUser.new() + twitch_banned_user.user_id = _user_id + twitch_banned_user.user_login = _user_login + twitch_banned_user.user_name = _user_name + twitch_banned_user.expires_at = _expires_at + twitch_banned_user.created_at = _created_at + twitch_banned_user.reason = _reason + twitch_banned_user.moderator_id = _moderator_id + twitch_banned_user.moderator_login = _moderator_login + twitch_banned_user.moderator_name = _moderator_name + return twitch_banned_user + + +static func from_json(d: Dictionary) -> TwitchBannedUser: + var result: TwitchBannedUser = TwitchBannedUser.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("expires_at", null) != null: + result.expires_at = d["expires_at"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("reason", null) != null: + result.reason = d["reason"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("moderator_login", null) != null: + result.moderator_login = d["moderator_login"] + if d.get("moderator_name", null) != null: + result.moderator_name = d["moderator_name"] + return result diff --git a/addons/twitcher/generated/twitch_banned_user.gd.uid b/addons/twitcher/generated/twitch_banned_user.gd.uid new file mode 100644 index 00000000..35a30ff0 --- /dev/null +++ b/addons/twitcher/generated/twitch_banned_user.gd.uid @@ -0,0 +1 @@ +uid://btn5nmya3pbly diff --git a/addons/twitcher/generated/twitch_bits_leaderboard.gd b/addons/twitcher/generated/twitch_bits_leaderboard.gd new file mode 100644 index 00000000..c46f846f --- /dev/null +++ b/addons/twitcher/generated/twitch_bits_leaderboard.gd @@ -0,0 +1,65 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/BitsLeaderboard +class_name TwitchBitsLeaderboard + +## An ID that identifies a user on the leaderboard. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The user’s position on the leaderboard. +@export var rank: int: + set(val): + rank = val + track_data(&"rank", val) + +## The number of Bits the user has cheered. +@export var score: int: + set(val): + score = val + track_data(&"score", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _user_name: String, _rank: int, _score: int) -> TwitchBitsLeaderboard: + var twitch_bits_leaderboard: TwitchBitsLeaderboard = TwitchBitsLeaderboard.new() + twitch_bits_leaderboard.user_id = _user_id + twitch_bits_leaderboard.user_login = _user_login + twitch_bits_leaderboard.user_name = _user_name + twitch_bits_leaderboard.rank = _rank + twitch_bits_leaderboard.score = _score + return twitch_bits_leaderboard + + +static func from_json(d: Dictionary) -> TwitchBitsLeaderboard: + var result: TwitchBitsLeaderboard = TwitchBitsLeaderboard.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("rank", null) != null: + result.rank = d["rank"] + if d.get("score", null) != null: + result.score = d["score"] + return result diff --git a/addons/twitcher/generated/twitch_bits_leaderboard.gd.uid b/addons/twitcher/generated/twitch_bits_leaderboard.gd.uid new file mode 100644 index 00000000..b2189a9b --- /dev/null +++ b/addons/twitcher/generated/twitch_bits_leaderboard.gd.uid @@ -0,0 +1 @@ +uid://ds3gv8nlj3sym diff --git a/addons/twitcher/generated/twitch_block_user.gd b/addons/twitcher/generated/twitch_block_user.gd new file mode 100644 index 00000000..a6d4dabd --- /dev/null +++ b/addons/twitcher/generated/twitch_block_user.gd @@ -0,0 +1,50 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchBlockUser + + + +## All optional parameters for TwitchAPI.block_user +## #/components/schemas/BlockUserOpt +class Opt extends TwitchData: + + ## The location where the harassment took place that is causing the brodcaster to block the user. Possible values are: + ## + ## * chat + ## * whisper + ## + ## . + @export var source_context: String: + set(val): + source_context = val + track_data(&"source_context", val) + + ## The reason that the broadcaster is blocking the user. Possible values are: + ## + ## * harassment + ## * spam + ## * other + @export var reason: String: + set(val): + reason = val + track_data(&"reason", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("source_context", null) != null: + result.source_context = d["source_context"] + if d.get("reason", null) != null: + result.reason = d["reason"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_block_user.gd.uid b/addons/twitcher/generated/twitch_block_user.gd.uid new file mode 100644 index 00000000..c8213776 --- /dev/null +++ b/addons/twitcher/generated/twitch_block_user.gd.uid @@ -0,0 +1 @@ +uid://cfvd224a7lj8w diff --git a/addons/twitcher/generated/twitch_blocked_term.gd b/addons/twitcher/generated/twitch_blocked_term.gd new file mode 100644 index 00000000..ffb34d4e --- /dev/null +++ b/addons/twitcher/generated/twitch_blocked_term.gd @@ -0,0 +1,87 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/BlockedTerm +class_name TwitchBlockedTerm + +## The broadcaster that owns the list of blocked terms. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The moderator that blocked the word or phrase from being used in the broadcaster’s chat room. +@export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + +## An ID that identifies this blocked term. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The blocked word or phrase. +@export var text: String: + set(val): + text = val + track_data(&"text", val) + +## The UTC date and time (in RFC3339 format) that the term was blocked. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The UTC date and time (in RFC3339 format) that the term was updated. +## +## When the term is added, this timestamp is the same as `created_at`. The timestamp changes as AutoMod continues to deny the term. +@export var updated_at: String: + set(val): + updated_at = val + track_data(&"updated_at", val) + +## The UTC date and time (in RFC3339 format) that the blocked term is set to expire. After the block expires, users may use the term in the broadcaster’s chat room. +## +## This field is **null** if the term was added manually or was permanently blocked by AutoMod. +@export var expires_at: String: + set(val): + expires_at = val + track_data(&"expires_at", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _moderator_id: String, _id: String, _text: String, _created_at: String, _updated_at: String, _expires_at: String) -> TwitchBlockedTerm: + var twitch_blocked_term: TwitchBlockedTerm = TwitchBlockedTerm.new() + twitch_blocked_term.broadcaster_id = _broadcaster_id + twitch_blocked_term.moderator_id = _moderator_id + twitch_blocked_term.id = _id + twitch_blocked_term.text = _text + twitch_blocked_term.created_at = _created_at + twitch_blocked_term.updated_at = _updated_at + twitch_blocked_term.expires_at = _expires_at + return twitch_blocked_term + + +static func from_json(d: Dictionary) -> TwitchBlockedTerm: + var result: TwitchBlockedTerm = TwitchBlockedTerm.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("text", null) != null: + result.text = d["text"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("updated_at", null) != null: + result.updated_at = d["updated_at"] + if d.get("expires_at", null) != null: + result.expires_at = d["expires_at"] + return result diff --git a/addons/twitcher/generated/twitch_blocked_term.gd.uid b/addons/twitcher/generated/twitch_blocked_term.gd.uid new file mode 100644 index 00000000..42c326ab --- /dev/null +++ b/addons/twitcher/generated/twitch_blocked_term.gd.uid @@ -0,0 +1 @@ +uid://dut1o64kt25wi diff --git a/addons/twitcher/generated/twitch_broadcaster_subscription.gd b/addons/twitcher/generated/twitch_broadcaster_subscription.gd new file mode 100644 index 00000000..fc35f494 --- /dev/null +++ b/addons/twitcher/generated/twitch_broadcaster_subscription.gd @@ -0,0 +1,132 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/BroadcasterSubscription +class_name TwitchBroadcasterSubscription + +## An ID that identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The ID of the user that gifted the subscription to the user. Is an empty string if `is_gift` is **false**. +@export var gifter_id: String: + set(val): + gifter_id = val + track_data(&"gifter_id", val) + +## The gifter’s login name. Is an empty string if `is_gift` is **false**. +@export var gifter_login: String: + set(val): + gifter_login = val + track_data(&"gifter_login", val) + +## The gifter’s display name. Is an empty string if `is_gift` is **false**. +@export var gifter_name: String: + set(val): + gifter_name = val + track_data(&"gifter_name", val) + +## A Boolean value that determines whether the subscription is a gift subscription. Is **true** if the subscription was gifted. +@export var is_gift: bool: + set(val): + is_gift = val + track_data(&"is_gift", val) + +## The name of the subscription. +@export var plan_name: String: + set(val): + plan_name = val + track_data(&"plan_name", val) + +## The type of subscription. Possible values are: +## +## * 1000 — Tier 1 +## * 2000 — Tier 2 +## * 3000 — Tier 3 +@export var tier: String: + set(val): + tier = val + track_data(&"tier", val) + +## An ID that identifies the subscribing user. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _gifter_id: String, _gifter_login: String, _gifter_name: String, _is_gift: bool, _plan_name: String, _tier: String, _user_id: String, _user_name: String, _user_login: String) -> TwitchBroadcasterSubscription: + var twitch_broadcaster_subscription: TwitchBroadcasterSubscription = TwitchBroadcasterSubscription.new() + twitch_broadcaster_subscription.broadcaster_id = _broadcaster_id + twitch_broadcaster_subscription.broadcaster_login = _broadcaster_login + twitch_broadcaster_subscription.broadcaster_name = _broadcaster_name + twitch_broadcaster_subscription.gifter_id = _gifter_id + twitch_broadcaster_subscription.gifter_login = _gifter_login + twitch_broadcaster_subscription.gifter_name = _gifter_name + twitch_broadcaster_subscription.is_gift = _is_gift + twitch_broadcaster_subscription.plan_name = _plan_name + twitch_broadcaster_subscription.tier = _tier + twitch_broadcaster_subscription.user_id = _user_id + twitch_broadcaster_subscription.user_name = _user_name + twitch_broadcaster_subscription.user_login = _user_login + return twitch_broadcaster_subscription + + +static func from_json(d: Dictionary) -> TwitchBroadcasterSubscription: + var result: TwitchBroadcasterSubscription = TwitchBroadcasterSubscription.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("gifter_id", null) != null: + result.gifter_id = d["gifter_id"] + if d.get("gifter_login", null) != null: + result.gifter_login = d["gifter_login"] + if d.get("gifter_name", null) != null: + result.gifter_name = d["gifter_name"] + if d.get("is_gift", null) != null: + result.is_gift = d["is_gift"] + if d.get("plan_name", null) != null: + result.plan_name = d["plan_name"] + if d.get("tier", null) != null: + result.tier = d["tier"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + return result diff --git a/addons/twitcher/generated/twitch_broadcaster_subscription.gd.uid b/addons/twitcher/generated/twitch_broadcaster_subscription.gd.uid new file mode 100644 index 00000000..64ddf9c2 --- /dev/null +++ b/addons/twitcher/generated/twitch_broadcaster_subscription.gd.uid @@ -0,0 +1 @@ +uid://dwjgwneb8uxui diff --git a/addons/twitcher/generated/twitch_category.gd b/addons/twitcher/generated/twitch_category.gd new file mode 100644 index 00000000..ce189d05 --- /dev/null +++ b/addons/twitcher/generated/twitch_category.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Category +class_name TwitchCategory + +## A URL to an image of the game’s box art or streaming category. +@export var box_art_url: String: + set(val): + box_art_url = val + track_data(&"box_art_url", val) + +## The name of the game or category. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## An ID that uniquely identifies the game or category. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + + + +## Constructor with all required fields. +static func create(_box_art_url: String, _name: String, _id: String) -> TwitchCategory: + var twitch_category: TwitchCategory = TwitchCategory.new() + twitch_category.box_art_url = _box_art_url + twitch_category.name = _name + twitch_category.id = _id + return twitch_category + + +static func from_json(d: Dictionary) -> TwitchCategory: + var result: TwitchCategory = TwitchCategory.new() + if d.get("box_art_url", null) != null: + result.box_art_url = d["box_art_url"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("id", null) != null: + result.id = d["id"] + return result diff --git a/addons/twitcher/generated/twitch_category.gd.uid b/addons/twitcher/generated/twitch_category.gd.uid new file mode 100644 index 00000000..34dff39c --- /dev/null +++ b/addons/twitcher/generated/twitch_category.gd.uid @@ -0,0 +1 @@ +uid://be2yia4ctewtx diff --git a/addons/twitcher/generated/twitch_channel.gd b/addons/twitcher/generated/twitch_channel.gd new file mode 100644 index 00000000..aab9777f --- /dev/null +++ b/addons/twitcher/generated/twitch_channel.gd @@ -0,0 +1,132 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Channel +class_name TwitchChannel + +## The ISO 639-1 two-letter language code of the language used by the broadcaster. For example, _en_ for English. If the broadcaster uses a language not in the list of [supported stream languages](https://help.twitch.tv/s/article/languages-on-twitch#streamlang), the value is _other_. +@export var broadcaster_language: String: + set(val): + broadcaster_language = val + track_data(&"broadcaster_language", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var display_name: String: + set(val): + display_name = val + track_data(&"display_name", val) + +## The ID of the game that the broadcaster is playing or last played. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The name of the game that the broadcaster is playing or last played. +@export var game_name: String: + set(val): + game_name = val + track_data(&"game_name", val) + +## An ID that uniquely identifies the channel (this is the broadcaster’s ID). +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## A Boolean value that determines whether the broadcaster is streaming live. Is **true** if the broadcaster is streaming live; otherwise, **false**. +@export var is_live: bool: + set(val): + is_live = val + track_data(&"is_live", val) + +## **IMPORTANT** As of February 28, 2023, this field is deprecated and returns only an empty array. If you use this field, please update your code to use the `tags` field. +## +## The list of tags that apply to the stream. The list contains IDs only when the channel is steaming live. For a list of possible tags, see [List of All Tags](https://www.twitch.tv/directory/all/tags). The list doesn’t include Category Tags. +@export var tag_ids: Array[String]: + set(val): + tag_ids = val + track_data(&"tag_ids", val) + +## The tags applied to the channel. +@export var tags: Array[String]: + set(val): + tags = val + track_data(&"tags", val) + +## A URL to a thumbnail of the broadcaster’s profile image. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## The stream’s title. Is an empty string if the broadcaster didn’t set it. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The UTC date and time (in RFC3339 format) of when the broadcaster started streaming. The string is empty if the broadcaster is not streaming live. +@export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_language: String, _broadcaster_login: String, _display_name: String, _game_id: String, _game_name: String, _id: String, _is_live: bool, _tag_ids: Array[String], _tags: Array[String], _thumbnail_url: String, _title: String, _started_at: String) -> TwitchChannel: + var twitch_channel: TwitchChannel = TwitchChannel.new() + twitch_channel.broadcaster_language = _broadcaster_language + twitch_channel.broadcaster_login = _broadcaster_login + twitch_channel.display_name = _display_name + twitch_channel.game_id = _game_id + twitch_channel.game_name = _game_name + twitch_channel.id = _id + twitch_channel.is_live = _is_live + twitch_channel.tag_ids = _tag_ids + twitch_channel.tags = _tags + twitch_channel.thumbnail_url = _thumbnail_url + twitch_channel.title = _title + twitch_channel.started_at = _started_at + return twitch_channel + + +static func from_json(d: Dictionary) -> TwitchChannel: + var result: TwitchChannel = TwitchChannel.new() + if d.get("broadcaster_language", null) != null: + result.broadcaster_language = d["broadcaster_language"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("display_name", null) != null: + result.display_name = d["display_name"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("game_name", null) != null: + result.game_name = d["game_name"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("is_live", null) != null: + result.is_live = d["is_live"] + if d.get("tag_ids", null) != null: + for value in d["tag_ids"]: + result.tag_ids.append(value) + if d.get("tags", null) != null: + for value in d["tags"]: + result.tags.append(value) + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + return result diff --git a/addons/twitcher/generated/twitch_channel.gd.uid b/addons/twitcher/generated/twitch_channel.gd.uid new file mode 100644 index 00000000..f8236725 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel.gd.uid @@ -0,0 +1 @@ +uid://b77eemd6nsdjf diff --git a/addons/twitcher/generated/twitch_channel_editor.gd b/addons/twitcher/generated/twitch_channel_editor.gd new file mode 100644 index 00000000..5c7dff4d --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_editor.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChannelEditor +class_name TwitchChannelEditor + +## An ID that uniquely identifies a user with editor permissions. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The date and time, in RFC3339 format, when the user became one of the broadcaster’s editors. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_name: String, _created_at: String) -> TwitchChannelEditor: + var twitch_channel_editor: TwitchChannelEditor = TwitchChannelEditor.new() + twitch_channel_editor.user_id = _user_id + twitch_channel_editor.user_name = _user_name + twitch_channel_editor.created_at = _created_at + return twitch_channel_editor + + +static func from_json(d: Dictionary) -> TwitchChannelEditor: + var result: TwitchChannelEditor = TwitchChannelEditor.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + return result diff --git a/addons/twitcher/generated/twitch_channel_editor.gd.uid b/addons/twitcher/generated/twitch_channel_editor.gd.uid new file mode 100644 index 00000000..84a8e750 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_editor.gd.uid @@ -0,0 +1 @@ +uid://ceah5b3esf15j diff --git a/addons/twitcher/generated/twitch_channel_emote.gd b/addons/twitcher/generated/twitch_channel_emote.gd new file mode 100644 index 00000000..a99a2655 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_emote.gd @@ -0,0 +1,168 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChannelEmote +class_name TwitchChannelEmote + +## An ID that identifies this emote. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +@export var images: Images: + set(val): + images = val + track_data(&"images", val) + +## The subscriber tier at which the emote is unlocked. This field contains the tier information only if `emote_type` is set to `subscriptions`, otherwise, it's an empty string. +@export var tier: String: + set(val): + tier = val + track_data(&"tier", val) + +## The type of emote. The possible values are: +## +## * bitstier — A custom Bits tier emote. +## * follower — A custom follower emote. +## * subscriptions — A custom subscriber emote. +@export var emote_type: String: + set(val): + emote_type = val + track_data(&"emote_type", val) + +## An ID that identifies the emote set that the emote belongs to. +@export var emote_set_id: String: + set(val): + emote_set_id = val + track_data(&"emote_set_id", val) + +## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only `static`. But if the emote is available as a static PNG and an animated GIF, the array contains `static` and `animated`. The possible formats are: +## +## * animated — An animated GIF is available for this emote. +## * static — A static PNG file is available for this emote. +@export var format: Array[String]: + set(val): + format = val + track_data(&"format", val) + +## The sizes that the emote is available in. For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0\. Possible sizes are: +## +## * 1.0 — A small version (28px x 28px) is available. +## * 2.0 — A medium version (56px x 56px) is available. +## * 3.0 — A large version (112px x 112px) is available. +@export var scale: Array[String]: + set(val): + scale = val + track_data(&"scale", val) + +## The background themes that the emote is available in. Possible themes are: +## +## * dark +## * light +@export var theme_mode: Array[String]: + set(val): + theme_mode = val + track_data(&"theme_mode", val) + + + +## Constructor with all required fields. +static func create(_id: String, _name: String, _images: Images, _tier: String, _emote_type: String, _emote_set_id: String, _format: Array[String], _scale: Array[String], _theme_mode: Array[String]) -> TwitchChannelEmote: + var twitch_channel_emote: TwitchChannelEmote = TwitchChannelEmote.new() + twitch_channel_emote.id = _id + twitch_channel_emote.name = _name + twitch_channel_emote.images = _images + twitch_channel_emote.tier = _tier + twitch_channel_emote.emote_type = _emote_type + twitch_channel_emote.emote_set_id = _emote_set_id + twitch_channel_emote.format = _format + twitch_channel_emote.scale = _scale + twitch_channel_emote.theme_mode = _theme_mode + return twitch_channel_emote + + +static func from_json(d: Dictionary) -> TwitchChannelEmote: + var result: TwitchChannelEmote = TwitchChannelEmote.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("images", null) != null: + result.images = Images.from_json(d["images"]) + if d.get("tier", null) != null: + result.tier = d["tier"] + if d.get("emote_type", null) != null: + result.emote_type = d["emote_type"] + if d.get("emote_set_id", null) != null: + result.emote_set_id = d["emote_set_id"] + if d.get("format", null) != null: + for value in d["format"]: + result.format.append(value) + if d.get("scale", null) != null: + for value in d["scale"]: + result.scale.append(value) + if d.get("theme_mode", null) != null: + for value in d["theme_mode"]: + result.theme_mode.append(value) + return result + + + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +## #/components/schemas/ChannelEmote/Images +class Images extends TwitchData: + + ## A URL to the small version (28px x 28px) of the emote. + @export var url_1x: String: + set(val): + url_1x = val + track_data(&"url_1x", val) + + ## A URL to the medium version (56px x 56px) of the emote. + @export var url_2x: String: + set(val): + url_2x = val + track_data(&"url_2x", val) + + ## A URL to the large version (112px x 112px) of the emote. + @export var url_4x: String: + set(val): + url_4x = val + track_data(&"url_4x", val) + + + + ## Constructor with all required fields. + static func create(_url_1x: String, _url_2x: String, _url_4x: String) -> Images: + var images: Images = Images.new() + images.url_1x = _url_1x + images.url_2x = _url_2x + images.url_4x = _url_4x + return images + + + static func from_json(d: Dictionary) -> Images: + var result: Images = Images.new() + if d.get("url_1x", null) != null: + result.url_1x = d["url_1x"] + if d.get("url_2x", null) != null: + result.url_2x = d["url_2x"] + if d.get("url_4x", null) != null: + result.url_4x = d["url_4x"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_channel_emote.gd.uid b/addons/twitcher/generated/twitch_channel_emote.gd.uid new file mode 100644 index 00000000..6359f00a --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_emote.gd.uid @@ -0,0 +1 @@ +uid://dsi08lylfjtyy diff --git a/addons/twitcher/generated/twitch_channel_information.gd b/addons/twitcher/generated/twitch_channel_information.gd new file mode 100644 index 00000000..89aad7d5 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_information.gd @@ -0,0 +1,121 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChannelInformation +class_name TwitchChannelInformation + +## An ID that uniquely identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The broadcaster’s preferred language. The value is an ISO 639-1 two-letter language code (for example, _en_ for English). The value is set to “other” if the language is not a Twitch supported language. +@export var broadcaster_language: String: + set(val): + broadcaster_language = val + track_data(&"broadcaster_language", val) + +## The name of the game that the broadcaster is playing or last played. The value is an empty string if the broadcaster has never played a game. +@export var game_name: String: + set(val): + game_name = val + track_data(&"game_name", val) + +## An ID that uniquely identifies the game that the broadcaster is playing or last played. The value is an empty string if the broadcaster has never played a game. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The title of the stream that the broadcaster is currently streaming or last streamed. The value is an empty string if the broadcaster has never streamed. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The value of the broadcaster’s stream delay setting, in seconds. This field’s value defaults to zero unless 1) the request specifies a user access token, 2) the ID in the _broadcaster\_id_ query parameter matches the user ID in the access token, and 3) the broadcaster has partner status and they set a non-zero stream delay value. +@export var delay: int: + set(val): + delay = val + track_data(&"delay", val) + +## The tags applied to the channel. +@export var tags: Array[String]: + set(val): + tags = val + track_data(&"tags", val) + +## The CCLs applied to the channel. +@export var content_classification_labels: Array[String]: + set(val): + content_classification_labels = val + track_data(&"content_classification_labels", val) + +## Boolean flag indicating if the channel has branded content. +@export var is_branded_content: bool: + set(val): + is_branded_content = val + track_data(&"is_branded_content", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _broadcaster_language: String, _game_name: String, _game_id: String, _title: String, _delay: int, _tags: Array[String], _content_classification_labels: Array[String], _is_branded_content: bool) -> TwitchChannelInformation: + var twitch_channel_information: TwitchChannelInformation = TwitchChannelInformation.new() + twitch_channel_information.broadcaster_id = _broadcaster_id + twitch_channel_information.broadcaster_login = _broadcaster_login + twitch_channel_information.broadcaster_name = _broadcaster_name + twitch_channel_information.broadcaster_language = _broadcaster_language + twitch_channel_information.game_name = _game_name + twitch_channel_information.game_id = _game_id + twitch_channel_information.title = _title + twitch_channel_information.delay = _delay + twitch_channel_information.tags = _tags + twitch_channel_information.content_classification_labels = _content_classification_labels + twitch_channel_information.is_branded_content = _is_branded_content + return twitch_channel_information + + +static func from_json(d: Dictionary) -> TwitchChannelInformation: + var result: TwitchChannelInformation = TwitchChannelInformation.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_language", null) != null: + result.broadcaster_language = d["broadcaster_language"] + if d.get("game_name", null) != null: + result.game_name = d["game_name"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("delay", null) != null: + result.delay = d["delay"] + if d.get("tags", null) != null: + for value in d["tags"]: + result.tags.append(value) + if d.get("content_classification_labels", null) != null: + for value in d["content_classification_labels"]: + result.content_classification_labels.append(value) + if d.get("is_branded_content", null) != null: + result.is_branded_content = d["is_branded_content"] + return result diff --git a/addons/twitcher/generated/twitch_channel_information.gd.uid b/addons/twitcher/generated/twitch_channel_information.gd.uid new file mode 100644 index 00000000..b27ce9b2 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_information.gd.uid @@ -0,0 +1 @@ +uid://cpne5mqp80ogl diff --git a/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd b/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd new file mode 100644 index 00000000..e40a6690 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd @@ -0,0 +1,120 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChannelStreamScheduleSegment +class_name TwitchChannelStreamScheduleSegment + +## An ID that identifies this broadcast segment. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The UTC date and time (in RFC3339 format) of when the broadcast starts. +@export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + +## The UTC date and time (in RFC3339 format) of when the broadcast ends. +@export var end_time: String: + set(val): + end_time = val + track_data(&"end_time", val) + +## The broadcast segment’s title. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## Indicates whether the broadcaster canceled this segment of a recurring broadcast. If the broadcaster canceled this segment, this field is set to the same value that’s in the `end_time` field; otherwise, it’s set to **null**. +@export var canceled_until: String: + set(val): + canceled_until = val + track_data(&"canceled_until", val) + +## The type of content that the broadcaster plans to stream or **null** if not specified. +@export var category: Category: + set(val): + category = val + track_data(&"category", val) + +## A Boolean value that determines whether the broadcast is part of a recurring series that streams at the same time each week or is a one-time broadcast. Is **true** if the broadcast is part of a recurring series. +@export var is_recurring: bool: + set(val): + is_recurring = val + track_data(&"is_recurring", val) + + + +## Constructor with all required fields. +static func create(_id: String, _start_time: String, _end_time: String, _title: String, _canceled_until: String, _category: Category, _is_recurring: bool) -> TwitchChannelStreamScheduleSegment: + var twitch_channel_stream_schedule_segment: TwitchChannelStreamScheduleSegment = TwitchChannelStreamScheduleSegment.new() + twitch_channel_stream_schedule_segment.id = _id + twitch_channel_stream_schedule_segment.start_time = _start_time + twitch_channel_stream_schedule_segment.end_time = _end_time + twitch_channel_stream_schedule_segment.title = _title + twitch_channel_stream_schedule_segment.canceled_until = _canceled_until + twitch_channel_stream_schedule_segment.category = _category + twitch_channel_stream_schedule_segment.is_recurring = _is_recurring + return twitch_channel_stream_schedule_segment + + +static func from_json(d: Dictionary) -> TwitchChannelStreamScheduleSegment: + var result: TwitchChannelStreamScheduleSegment = TwitchChannelStreamScheduleSegment.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("end_time", null) != null: + result.end_time = d["end_time"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("canceled_until", null) != null: + result.canceled_until = d["canceled_until"] + if d.get("category", null) != null: + result.category = Category.from_json(d["category"]) + if d.get("is_recurring", null) != null: + result.is_recurring = d["is_recurring"] + return result + + + +## The type of content that the broadcaster plans to stream or **null** if not specified. +## #/components/schemas/ChannelStreamScheduleSegment/Category +class Category extends TwitchData: + + ## An ID that identifies the category that best represents the content that the broadcaster plans to stream. For example, the game’s ID if the broadcaster will play a game or the Just Chatting ID if the broadcaster will host a talk show. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The name of the category. For example, the game’s title if the broadcaster will play a game or Just Chatting if the broadcaster will host a talk show. + @export var name: String: + set(val): + name = val + track_data(&"name", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _name: String) -> Category: + var category: Category = Category.new() + category.id = _id + category.name = _name + return category + + + static func from_json(d: Dictionary) -> Category: + var result: Category = Category.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd.uid b/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd.uid new file mode 100644 index 00000000..371c7b3a --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_stream_schedule_segment.gd.uid @@ -0,0 +1 @@ +uid://cnuxysxl4ho1 diff --git a/addons/twitcher/generated/twitch_channel_team.gd b/addons/twitcher/generated/twitch_channel_team.gd new file mode 100644 index 00000000..2a33f4e1 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_team.gd @@ -0,0 +1,128 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChannelTeam +class_name TwitchChannelTeam + +## An ID that identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## A URL to the team’s background image. +@export var background_image_url: String: + set(val): + background_image_url = val + track_data(&"background_image_url", val) + +## A URL to the team’s banner. +@export var banner: String: + set(val): + banner = val + track_data(&"banner", val) + +## The UTC date and time (in RFC3339 format) of when the team was created. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The UTC date and time (in RFC3339 format) of the last time the team was updated. +@export var updated_at: String: + set(val): + updated_at = val + track_data(&"updated_at", val) + +## The team’s description. The description may contain formatting such as Markdown, HTML, newline (\\n) characters, etc. +@export var info: String: + set(val): + info = val + track_data(&"info", val) + +## A URL to a thumbnail image of the team’s logo. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## The team’s name. +@export var team_name: String: + set(val): + team_name = val + track_data(&"team_name", val) + +## The team’s display name. +@export var team_display_name: String: + set(val): + team_display_name = val + track_data(&"team_display_name", val) + +## An ID that identifies the team. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _background_image_url: String, _banner: String, _created_at: String, _updated_at: String, _info: String, _thumbnail_url: String, _team_name: String, _team_display_name: String, _id: String) -> TwitchChannelTeam: + var twitch_channel_team: TwitchChannelTeam = TwitchChannelTeam.new() + twitch_channel_team.broadcaster_id = _broadcaster_id + twitch_channel_team.broadcaster_login = _broadcaster_login + twitch_channel_team.broadcaster_name = _broadcaster_name + twitch_channel_team.background_image_url = _background_image_url + twitch_channel_team.banner = _banner + twitch_channel_team.created_at = _created_at + twitch_channel_team.updated_at = _updated_at + twitch_channel_team.info = _info + twitch_channel_team.thumbnail_url = _thumbnail_url + twitch_channel_team.team_name = _team_name + twitch_channel_team.team_display_name = _team_display_name + twitch_channel_team.id = _id + return twitch_channel_team + + +static func from_json(d: Dictionary) -> TwitchChannelTeam: + var result: TwitchChannelTeam = TwitchChannelTeam.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("background_image_url", null) != null: + result.background_image_url = d["background_image_url"] + if d.get("banner", null) != null: + result.banner = d["banner"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("updated_at", null) != null: + result.updated_at = d["updated_at"] + if d.get("info", null) != null: + result.info = d["info"] + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("team_name", null) != null: + result.team_name = d["team_name"] + if d.get("team_display_name", null) != null: + result.team_display_name = d["team_display_name"] + if d.get("id", null) != null: + result.id = d["id"] + return result diff --git a/addons/twitcher/generated/twitch_channel_team.gd.uid b/addons/twitcher/generated/twitch_channel_team.gd.uid new file mode 100644 index 00000000..efd5c7b0 --- /dev/null +++ b/addons/twitcher/generated/twitch_channel_team.gd.uid @@ -0,0 +1 @@ +uid://bcbfc8frdl2f1 diff --git a/addons/twitcher/generated/twitch_charity_campaign.gd b/addons/twitcher/generated/twitch_charity_campaign.gd new file mode 100644 index 00000000..e3ce3c70 --- /dev/null +++ b/addons/twitcher/generated/twitch_charity_campaign.gd @@ -0,0 +1,205 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CharityCampaign +class_name TwitchCharityCampaign + +## An ID that identifies the charity campaign. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the broadcaster that’s running the campaign. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The charity’s name. +@export var charity_name: String: + set(val): + charity_name = val + track_data(&"charity_name", val) + +## A description of the charity. +@export var charity_description: String: + set(val): + charity_description = val + track_data(&"charity_description", val) + +## A URL to an image of the charity’s logo. The image’s type is PNG and its size is 100px X 100px. +@export var charity_logo: String: + set(val): + charity_logo = val + track_data(&"charity_logo", val) + +## A URL to the charity’s website. +@export var charity_website: String: + set(val): + charity_website = val + track_data(&"charity_website", val) + +## The current amount of donations that the campaign has received. +@export var current_amount: CurrentAmount: + set(val): + current_amount = val + track_data(&"current_amount", val) + +## The campaign’s fundraising goal. This field is **null** if the broadcaster has not defined a fundraising goal. +@export var target_amount: TargetAmount: + set(val): + target_amount = val + track_data(&"target_amount", val) + + + +## Constructor with all required fields. +static func create(_id: String, _broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _charity_name: String, _charity_description: String, _charity_logo: String, _charity_website: String, _current_amount: CurrentAmount, _target_amount: TargetAmount) -> TwitchCharityCampaign: + var twitch_charity_campaign: TwitchCharityCampaign = TwitchCharityCampaign.new() + twitch_charity_campaign.id = _id + twitch_charity_campaign.broadcaster_id = _broadcaster_id + twitch_charity_campaign.broadcaster_login = _broadcaster_login + twitch_charity_campaign.broadcaster_name = _broadcaster_name + twitch_charity_campaign.charity_name = _charity_name + twitch_charity_campaign.charity_description = _charity_description + twitch_charity_campaign.charity_logo = _charity_logo + twitch_charity_campaign.charity_website = _charity_website + twitch_charity_campaign.current_amount = _current_amount + twitch_charity_campaign.target_amount = _target_amount + return twitch_charity_campaign + + +static func from_json(d: Dictionary) -> TwitchCharityCampaign: + var result: TwitchCharityCampaign = TwitchCharityCampaign.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("charity_name", null) != null: + result.charity_name = d["charity_name"] + if d.get("charity_description", null) != null: + result.charity_description = d["charity_description"] + if d.get("charity_logo", null) != null: + result.charity_logo = d["charity_logo"] + if d.get("charity_website", null) != null: + result.charity_website = d["charity_website"] + if d.get("current_amount", null) != null: + result.current_amount = CurrentAmount.from_json(d["current_amount"]) + if d.get("target_amount", null) != null: + result.target_amount = TargetAmount.from_json(d["target_amount"]) + return result + + + +## The current amount of donations that the campaign has received. +## #/components/schemas/CharityCampaign/CurrentAmount +class CurrentAmount extends TwitchData: + + ## The monetary amount. The amount is specified in the currency’s minor unit. For example, the minor units for USD is cents, so if the amount is $5.50 USD, `value` is set to 550. + @export var value: int: + set(val): + value = val + track_data(&"value", val) + + ## The number of decimal places used by the currency. For example, USD uses two decimal places. Use this number to translate `value` from minor units to major units by using the formula: + ## + ## `value / 10^decimal_places` + @export var decimal_places: int: + set(val): + decimal_places = val + track_data(&"decimal_places", val) + + ## The ISO-4217 three-letter currency code that identifies the type of currency in `value`. + @export var currency: String: + set(val): + currency = val + track_data(&"currency", val) + + + + ## Constructor with all required fields. + static func create(_value: int, _decimal_places: int, _currency: String) -> CurrentAmount: + var current_amount: CurrentAmount = CurrentAmount.new() + current_amount.value = _value + current_amount.decimal_places = _decimal_places + current_amount.currency = _currency + return current_amount + + + static func from_json(d: Dictionary) -> CurrentAmount: + var result: CurrentAmount = CurrentAmount.new() + if d.get("value", null) != null: + result.value = d["value"] + if d.get("decimal_places", null) != null: + result.decimal_places = d["decimal_places"] + if d.get("currency", null) != null: + result.currency = d["currency"] + return result + + + +## The campaign’s fundraising goal. This field is **null** if the broadcaster has not defined a fundraising goal. +## #/components/schemas/CharityCampaign/TargetAmount +class TargetAmount extends TwitchData: + + ## The monetary amount. The amount is specified in the currency’s minor unit. For example, the minor units for USD is cents, so if the amount is $5.50 USD, `value` is set to 550. + @export var value: int: + set(val): + value = val + track_data(&"value", val) + + ## The number of decimal places used by the currency. For example, USD uses two decimal places. Use this number to translate `value` from minor units to major units by using the formula: + ## + ## `value / 10^decimal_places` + @export var decimal_places: int: + set(val): + decimal_places = val + track_data(&"decimal_places", val) + + ## The ISO-4217 three-letter currency code that identifies the type of currency in `value`. + @export var currency: String: + set(val): + currency = val + track_data(&"currency", val) + + + + ## Constructor with all required fields. + static func create(_value: int, _decimal_places: int, _currency: String) -> TargetAmount: + var target_amount: TargetAmount = TargetAmount.new() + target_amount.value = _value + target_amount.decimal_places = _decimal_places + target_amount.currency = _currency + return target_amount + + + static func from_json(d: Dictionary) -> TargetAmount: + var result: TargetAmount = TargetAmount.new() + if d.get("value", null) != null: + result.value = d["value"] + if d.get("decimal_places", null) != null: + result.decimal_places = d["decimal_places"] + if d.get("currency", null) != null: + result.currency = d["currency"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_charity_campaign.gd.uid b/addons/twitcher/generated/twitch_charity_campaign.gd.uid new file mode 100644 index 00000000..b908bd30 --- /dev/null +++ b/addons/twitcher/generated/twitch_charity_campaign.gd.uid @@ -0,0 +1 @@ +uid://bfccgp4ggf3cs diff --git a/addons/twitcher/generated/twitch_charity_campaign_donation.gd b/addons/twitcher/generated/twitch_charity_campaign_donation.gd new file mode 100644 index 00000000..9d3c2ccc --- /dev/null +++ b/addons/twitcher/generated/twitch_charity_campaign_donation.gd @@ -0,0 +1,122 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CharityCampaignDonation +class_name TwitchCharityCampaignDonation + +## An ID that identifies the donation. The ID is unique across campaigns. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the charity campaign that the donation applies to. +@export var campaign_id: String: + set(val): + campaign_id = val + track_data(&"campaign_id", val) + +## An ID that identifies a user that donated money to the campaign. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## An object that contains the amount of money that the user donated. +@export var amount: Amount: + set(val): + amount = val + track_data(&"amount", val) + + + +## Constructor with all required fields. +static func create(_id: String, _campaign_id: String, _user_id: String, _user_login: String, _user_name: String, _amount: Amount) -> TwitchCharityCampaignDonation: + var twitch_charity_campaign_donation: TwitchCharityCampaignDonation = TwitchCharityCampaignDonation.new() + twitch_charity_campaign_donation.id = _id + twitch_charity_campaign_donation.campaign_id = _campaign_id + twitch_charity_campaign_donation.user_id = _user_id + twitch_charity_campaign_donation.user_login = _user_login + twitch_charity_campaign_donation.user_name = _user_name + twitch_charity_campaign_donation.amount = _amount + return twitch_charity_campaign_donation + + +static func from_json(d: Dictionary) -> TwitchCharityCampaignDonation: + var result: TwitchCharityCampaignDonation = TwitchCharityCampaignDonation.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("campaign_id", null) != null: + result.campaign_id = d["campaign_id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("amount", null) != null: + result.amount = Amount.from_json(d["amount"]) + return result + + + +## An object that contains the amount of money that the user donated. +## #/components/schemas/CharityCampaignDonation/Amount +class Amount extends TwitchData: + + ## The monetary amount. The amount is specified in the currency’s minor unit. For example, the minor units for USD is cents, so if the amount is $5.50 USD, `value` is set to 550. + @export var value: int: + set(val): + value = val + track_data(&"value", val) + + ## The number of decimal places used by the currency. For example, USD uses two decimal places. Use this number to translate `value` from minor units to major units by using the formula: + ## + ## `value / 10^decimal_places` + @export var decimal_places: int: + set(val): + decimal_places = val + track_data(&"decimal_places", val) + + ## The ISO-4217 three-letter currency code that identifies the type of currency in `value`. + @export var currency: String: + set(val): + currency = val + track_data(&"currency", val) + + + + ## Constructor with all required fields. + static func create(_value: int, _decimal_places: int, _currency: String) -> Amount: + var amount: Amount = Amount.new() + amount.value = _value + amount.decimal_places = _decimal_places + amount.currency = _currency + return amount + + + static func from_json(d: Dictionary) -> Amount: + var result: Amount = Amount.new() + if d.get("value", null) != null: + result.value = d["value"] + if d.get("decimal_places", null) != null: + result.decimal_places = d["decimal_places"] + if d.get("currency", null) != null: + result.currency = d["currency"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_charity_campaign_donation.gd.uid b/addons/twitcher/generated/twitch_charity_campaign_donation.gd.uid new file mode 100644 index 00000000..f854a974 --- /dev/null +++ b/addons/twitcher/generated/twitch_charity_campaign_donation.gd.uid @@ -0,0 +1 @@ +uid://bk4mrla21bejo diff --git a/addons/twitcher/generated/twitch_chat_badge.gd b/addons/twitcher/generated/twitch_chat_badge.gd new file mode 100644 index 00000000..e9a1901d --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_badge.gd @@ -0,0 +1,130 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChatBadge +class_name TwitchChatBadge + +## An ID that identifies this set of chat badges. For example, Bits or Subscriber. +@export var set_id: String: + set(val): + set_id = val + track_data(&"set_id", val) + +## The list of chat badges in this set. +@export var versions: Array[Versions]: + set(val): + versions = val + track_data(&"versions", val) + + + +## Constructor with all required fields. +static func create(_set_id: String, _versions: Array[Versions]) -> TwitchChatBadge: + var twitch_chat_badge: TwitchChatBadge = TwitchChatBadge.new() + twitch_chat_badge.set_id = _set_id + twitch_chat_badge.versions = _versions + return twitch_chat_badge + + +static func from_json(d: Dictionary) -> TwitchChatBadge: + var result: TwitchChatBadge = TwitchChatBadge.new() + if d.get("set_id", null) != null: + result.set_id = d["set_id"] + if d.get("versions", null) != null: + for value in d["versions"]: + result.versions.append(Versions.from_json(value)) + return result + + + +## The list of chat badges in this set. +## #/components/schemas/ChatBadge/Versions +class Versions extends TwitchData: + + ## An ID that identifies this version of the badge. The ID can be any value. For example, for Bits, the ID is the Bits tier level, but for World of Warcraft, it could be Alliance or Horde. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## A URL to the small version (18px x 18px) of the badge. + @export var image_url_1x: String: + set(val): + image_url_1x = val + track_data(&"image_url_1x", val) + + ## A URL to the medium version (36px x 36px) of the badge. + @export var image_url_2x: String: + set(val): + image_url_2x = val + track_data(&"image_url_2x", val) + + ## A URL to the large version (72px x 72px) of the badge. + @export var image_url_4x: String: + set(val): + image_url_4x = val + track_data(&"image_url_4x", val) + + ## The title of the badge. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The description of the badge. + @export var description: String: + set(val): + description = val + track_data(&"description", val) + + ## The action to take when clicking on the badge. Set to `null` if no action is specified. + @export var click_action: String: + set(val): + click_action = val + track_data(&"click_action", val) + + ## The URL to navigate to when clicking on the badge. Set to `null` if no URL is specified. + @export var click_url: String: + set(val): + click_url = val + track_data(&"click_url", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _image_url_1x: String, _image_url_2x: String, _image_url_4x: String, _title: String, _description: String, _click_action: String, _click_url: String) -> Versions: + var versions: Versions = Versions.new() + versions.id = _id + versions.image_url_1x = _image_url_1x + versions.image_url_2x = _image_url_2x + versions.image_url_4x = _image_url_4x + versions.title = _title + versions.description = _description + versions.click_action = _click_action + versions.click_url = _click_url + return versions + + + static func from_json(d: Dictionary) -> Versions: + var result: Versions = Versions.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("image_url_1x", null) != null: + result.image_url_1x = d["image_url_1x"] + if d.get("image_url_2x", null) != null: + result.image_url_2x = d["image_url_2x"] + if d.get("image_url_4x", null) != null: + result.image_url_4x = d["image_url_4x"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("click_action", null) != null: + result.click_action = d["click_action"] + if d.get("click_url", null) != null: + result.click_url = d["click_url"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_chat_badge.gd.uid b/addons/twitcher/generated/twitch_chat_badge.gd.uid new file mode 100644 index 00000000..546acaee --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_badge.gd.uid @@ -0,0 +1 @@ +uid://b0gnrrvi7p5av diff --git a/addons/twitcher/generated/twitch_chat_settings.gd b/addons/twitcher/generated/twitch_chat_settings.gd new file mode 100644 index 00000000..401a437e --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_settings.gd @@ -0,0 +1,134 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChatSettings +class_name TwitchChatSettings + +## The ID of the broadcaster specified in the request. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## A Boolean value that determines whether chat messages must contain only emotes. Is **true** if chat messages may contain only emotes; otherwise, **false**. +@export var emote_mode: bool: + set(val): + emote_mode = val + track_data(&"emote_mode", val) + +## A Boolean value that determines whether the broadcaster restricts the chat room to followers only. +## +## Is **true** if the broadcaster restricts the chat room to followers only; otherwise, **false**. +## +## See the `follower_mode_duration` field for how long users must follow the broadcaster before being able to participate in the chat room. +@export var follower_mode: bool: + set(val): + follower_mode = val + track_data(&"follower_mode", val) + +## The length of time, in minutes, that users must follow the broadcaster before being able to participate in the chat room. Is **null** if `follower_mode` is **false**. +@export var follower_mode_duration: int: + set(val): + follower_mode_duration = val + track_data(&"follower_mode_duration", val) + +## The moderator’s ID. The response includes this field only if the request specifies a user access token that includes the **moderator:read:chat\_settings** scope. +@export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + +## A Boolean value that determines whether the broadcaster adds a short delay before chat messages appear in the chat room. This gives chat moderators and bots a chance to remove them before viewers can see the message. See the `non_moderator_chat_delay_duration` field for the length of the delay. Is **true** if the broadcaster applies a delay; otherwise, **false**. +## +## The response includes this field only if the request specifies a user access token that includes the **moderator:read:chat\_settings** scope and the user in the _moderator\_id_ query parameter is one of the broadcaster’s moderators. +@export var non_moderator_chat_delay: bool: + set(val): + non_moderator_chat_delay = val + track_data(&"non_moderator_chat_delay", val) + +## The amount of time, in seconds, that messages are delayed before appearing in chat. Is **null** if `non_moderator_chat_delay` is **false**. +## +## The response includes this field only if the request specifies a user access token that includes the **moderator:read:chat\_settings** scope and the user in the _moderator\_id_ query parameter is one of the broadcaster’s moderators. +@export var non_moderator_chat_delay_duration: int: + set(val): + non_moderator_chat_delay_duration = val + track_data(&"non_moderator_chat_delay_duration", val) + +## A Boolean value that determines whether the broadcaster limits how often users in the chat room are allowed to send messages. +## +## Is **true** if the broadcaster applies a delay; otherwise, **false**. +## +## See the `slow_mode_wait_time` field for the delay. +@export var slow_mode: bool: + set(val): + slow_mode = val + track_data(&"slow_mode", val) + +## The amount of time, in seconds, that users must wait between sending messages. +## +## Is **null** if slow\_mode is **false**. +@export var slow_mode_wait_time: int: + set(val): + slow_mode_wait_time = val + track_data(&"slow_mode_wait_time", val) + +## A Boolean value that determines whether only users that subscribe to the broadcaster’s channel may talk in the chat room. +## +## Is **true** if the broadcaster restricts the chat room to subscribers only; otherwise, **false**. +@export var subscriber_mode: bool: + set(val): + subscriber_mode = val + track_data(&"subscriber_mode", val) + +## A Boolean value that determines whether the broadcaster requires users to post only unique messages in the chat room. +## +## Is **true** if the broadcaster requires unique messages only; otherwise, **false**. +@export var unique_chat_mode: bool: + set(val): + unique_chat_mode = val + track_data(&"unique_chat_mode", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _emote_mode: bool, _follower_mode: bool, _follower_mode_duration: int, _slow_mode: bool, _slow_mode_wait_time: int, _subscriber_mode: bool, _unique_chat_mode: bool) -> TwitchChatSettings: + var twitch_chat_settings: TwitchChatSettings = TwitchChatSettings.new() + twitch_chat_settings.broadcaster_id = _broadcaster_id + twitch_chat_settings.emote_mode = _emote_mode + twitch_chat_settings.follower_mode = _follower_mode + twitch_chat_settings.follower_mode_duration = _follower_mode_duration + twitch_chat_settings.slow_mode = _slow_mode + twitch_chat_settings.slow_mode_wait_time = _slow_mode_wait_time + twitch_chat_settings.subscriber_mode = _subscriber_mode + twitch_chat_settings.unique_chat_mode = _unique_chat_mode + return twitch_chat_settings + + +static func from_json(d: Dictionary) -> TwitchChatSettings: + var result: TwitchChatSettings = TwitchChatSettings.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("emote_mode", null) != null: + result.emote_mode = d["emote_mode"] + if d.get("follower_mode", null) != null: + result.follower_mode = d["follower_mode"] + if d.get("follower_mode_duration", null) != null: + result.follower_mode_duration = d["follower_mode_duration"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("non_moderator_chat_delay", null) != null: + result.non_moderator_chat_delay = d["non_moderator_chat_delay"] + if d.get("non_moderator_chat_delay_duration", null) != null: + result.non_moderator_chat_delay_duration = d["non_moderator_chat_delay_duration"] + if d.get("slow_mode", null) != null: + result.slow_mode = d["slow_mode"] + if d.get("slow_mode_wait_time", null) != null: + result.slow_mode_wait_time = d["slow_mode_wait_time"] + if d.get("subscriber_mode", null) != null: + result.subscriber_mode = d["subscriber_mode"] + if d.get("unique_chat_mode", null) != null: + result.unique_chat_mode = d["unique_chat_mode"] + return result diff --git a/addons/twitcher/generated/twitch_chat_settings.gd.uid b/addons/twitcher/generated/twitch_chat_settings.gd.uid new file mode 100644 index 00000000..ac350c0a --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_settings.gd.uid @@ -0,0 +1 @@ +uid://d10yuxg35qlpa diff --git a/addons/twitcher/generated/twitch_chat_settings_updated.gd b/addons/twitcher/generated/twitch_chat_settings_updated.gd new file mode 100644 index 00000000..b11f689d --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_settings_updated.gd @@ -0,0 +1,132 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ChatSettingsUpdated +class_name TwitchChatSettingsUpdated + +## The ID of the broadcaster specified in the request. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## A Boolean value that determines whether chat messages must contain only emotes. Is **true** if chat messages may contain only emotes; otherwise, **false**. +@export var emote_mode: bool: + set(val): + emote_mode = val + track_data(&"emote_mode", val) + +## A Boolean value that determines whether the broadcaster restricts the chat room to followers only. +## +## Is **true** if the broadcaster restricts the chat room to followers only; otherwise, **false**. +## +## See the `follower_mode_duration` field for how long users must follow the broadcaster before being able to participate in the chat room. +@export var follower_mode: bool: + set(val): + follower_mode = val + track_data(&"follower_mode", val) + +## The length of time, in minutes, that users must follow the broadcaster before being able to participate in the chat room. Is **null** if `follower_mode` is **false**. +@export var follower_mode_duration: int: + set(val): + follower_mode_duration = val + track_data(&"follower_mode_duration", val) + +## The moderator’s ID. The response includes this field only if the request specifies a user access token that includes the **moderator:read:chat\_settings** scope. +@export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + +## A Boolean value that determines whether the broadcaster adds a short delay before chat messages appear in the chat room. This gives chat moderators and bots a chance to remove them before viewers can see the message. See the `non_moderator_chat_delay_duration` field for the length of the delay. Is **true** if the broadcaster applies a delay; otherwise, **false**. +@export var non_moderator_chat_delay: bool: + set(val): + non_moderator_chat_delay = val + track_data(&"non_moderator_chat_delay", val) + +## The amount of time, in seconds, that messages are delayed before appearing in chat. Is **null** if `non_moderator_chat_delay` is **false**. +@export var non_moderator_chat_delay_duration: int: + set(val): + non_moderator_chat_delay_duration = val + track_data(&"non_moderator_chat_delay_duration", val) + +## A Boolean value that determines whether the broadcaster limits how often users in the chat room are allowed to send messages. +## +## Is **true** if the broadcaster applies a delay; otherwise, **false**. +## +## See the `slow_mode_wait_time` field for the delay. +@export var slow_mode: bool: + set(val): + slow_mode = val + track_data(&"slow_mode", val) + +## The amount of time, in seconds, that users must wait between sending messages. +## +## Is **null** if slow\_mode is **false**. +@export var slow_mode_wait_time: int: + set(val): + slow_mode_wait_time = val + track_data(&"slow_mode_wait_time", val) + +## A Boolean value that determines whether only users that subscribe to the broadcaster’s channel may talk in the chat room. +## +## Is **true** if the broadcaster restricts the chat room to subscribers only; otherwise, **false**. +@export var subscriber_mode: bool: + set(val): + subscriber_mode = val + track_data(&"subscriber_mode", val) + +## A Boolean value that determines whether the broadcaster requires users to post only unique messages in the chat room. +## +## Is **true** if the broadcaster requires unique messages only; otherwise, **false**. +@export var unique_chat_mode: bool: + set(val): + unique_chat_mode = val + track_data(&"unique_chat_mode", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _emote_mode: bool, _follower_mode: bool, _follower_mode_duration: int, _non_moderator_chat_delay: bool, _non_moderator_chat_delay_duration: int, _slow_mode: bool, _slow_mode_wait_time: int, _subscriber_mode: bool, _unique_chat_mode: bool) -> TwitchChatSettingsUpdated: + var twitch_chat_settings_updated: TwitchChatSettingsUpdated = TwitchChatSettingsUpdated.new() + twitch_chat_settings_updated.broadcaster_id = _broadcaster_id + twitch_chat_settings_updated.emote_mode = _emote_mode + twitch_chat_settings_updated.follower_mode = _follower_mode + twitch_chat_settings_updated.follower_mode_duration = _follower_mode_duration + twitch_chat_settings_updated.non_moderator_chat_delay = _non_moderator_chat_delay + twitch_chat_settings_updated.non_moderator_chat_delay_duration = _non_moderator_chat_delay_duration + twitch_chat_settings_updated.slow_mode = _slow_mode + twitch_chat_settings_updated.slow_mode_wait_time = _slow_mode_wait_time + twitch_chat_settings_updated.subscriber_mode = _subscriber_mode + twitch_chat_settings_updated.unique_chat_mode = _unique_chat_mode + return twitch_chat_settings_updated + + +static func from_json(d: Dictionary) -> TwitchChatSettingsUpdated: + var result: TwitchChatSettingsUpdated = TwitchChatSettingsUpdated.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("emote_mode", null) != null: + result.emote_mode = d["emote_mode"] + if d.get("follower_mode", null) != null: + result.follower_mode = d["follower_mode"] + if d.get("follower_mode_duration", null) != null: + result.follower_mode_duration = d["follower_mode_duration"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("non_moderator_chat_delay", null) != null: + result.non_moderator_chat_delay = d["non_moderator_chat_delay"] + if d.get("non_moderator_chat_delay_duration", null) != null: + result.non_moderator_chat_delay_duration = d["non_moderator_chat_delay_duration"] + if d.get("slow_mode", null) != null: + result.slow_mode = d["slow_mode"] + if d.get("slow_mode_wait_time", null) != null: + result.slow_mode_wait_time = d["slow_mode_wait_time"] + if d.get("subscriber_mode", null) != null: + result.subscriber_mode = d["subscriber_mode"] + if d.get("unique_chat_mode", null) != null: + result.unique_chat_mode = d["unique_chat_mode"] + return result diff --git a/addons/twitcher/generated/twitch_chat_settings_updated.gd.uid b/addons/twitcher/generated/twitch_chat_settings_updated.gd.uid new file mode 100644 index 00000000..f4f912d0 --- /dev/null +++ b/addons/twitcher/generated/twitch_chat_settings_updated.gd.uid @@ -0,0 +1 @@ +uid://rp8u05k22h77 diff --git a/addons/twitcher/generated/twitch_chatter.gd b/addons/twitcher/generated/twitch_chatter.gd new file mode 100644 index 00000000..28457525 --- /dev/null +++ b/addons/twitcher/generated/twitch_chatter.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Chatter +class_name TwitchChatter + +## The ID of a user that’s connected to the broadcaster’s chat room. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _user_name: String) -> TwitchChatter: + var twitch_chatter: TwitchChatter = TwitchChatter.new() + twitch_chatter.user_id = _user_id + twitch_chatter.user_login = _user_login + twitch_chatter.user_name = _user_name + return twitch_chatter + + +static func from_json(d: Dictionary) -> TwitchChatter: + var result: TwitchChatter = TwitchChatter.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + return result diff --git a/addons/twitcher/generated/twitch_chatter.gd.uid b/addons/twitcher/generated/twitch_chatter.gd.uid new file mode 100644 index 00000000..e65350e2 --- /dev/null +++ b/addons/twitcher/generated/twitch_chatter.gd.uid @@ -0,0 +1 @@ +uid://d0och515dnpht diff --git a/addons/twitcher/generated/twitch_check_auto_mod_status.gd b/addons/twitcher/generated/twitch_check_auto_mod_status.gd new file mode 100644 index 00000000..23d4bb38 --- /dev/null +++ b/addons/twitcher/generated/twitch_check_auto_mod_status.gd @@ -0,0 +1,99 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCheckAutoModStatus + + + +## +## #/components/schemas/CheckAutoModStatusBody +class Body extends TwitchData: + + ## The list of messages to check. The list must contain at least one message and may contain up to a maximum of 100 messages. + @export var data: Array[BodyData]: + set(val): + data = val + track_data(&"data", val) + + + + ## Constructor with all required fields. + static func create(_data: Array[BodyData]) -> Body: + var body: Body = Body.new() + body.data = _data + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(BodyData.from_json(value)) + return result + + + +## The list of messages to check. The list must contain at least one message and may contain up to a maximum of 100 messages. +## #/components/schemas/CheckAutoModStatusBody/Data +class BodyData extends TwitchData: + + ## A caller-defined ID used to correlate this message with the same message in the response. + @export var msg_id: String: + set(val): + msg_id = val + track_data(&"msg_id", val) + + ## The message to check. + @export var msg_text: String: + set(val): + msg_text = val + track_data(&"msg_text", val) + + + + ## Constructor with all required fields. + static func create(_msg_id: String, _msg_text: String) -> BodyData: + var body_data: BodyData = BodyData.new() + body_data.msg_id = _msg_id + body_data.msg_text = _msg_text + return body_data + + + static func from_json(d: Dictionary) -> BodyData: + var result: BodyData = BodyData.new() + if d.get("msg_id", null) != null: + result.msg_id = d["msg_id"] + if d.get("msg_text", null) != null: + result.msg_text = d["msg_text"] + return result + + + +## +## #/components/schemas/CheckAutoModStatusResponse +class Response extends TwitchData: + + ## The list of messages and whether Twitch would approve them for chat. + @export var data: Array[TwitchAutoModStatus]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchAutoModStatus]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchAutoModStatus.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_check_auto_mod_status.gd.uid b/addons/twitcher/generated/twitch_check_auto_mod_status.gd.uid new file mode 100644 index 00000000..f64e68b1 --- /dev/null +++ b/addons/twitcher/generated/twitch_check_auto_mod_status.gd.uid @@ -0,0 +1 @@ +uid://cvx1r2bhj5cfk diff --git a/addons/twitcher/generated/twitch_check_user_subscription.gd b/addons/twitcher/generated/twitch_check_user_subscription.gd new file mode 100644 index 00000000..b906ecdb --- /dev/null +++ b/addons/twitcher/generated/twitch_check_user_subscription.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCheckUserSubscription + + + +## +## #/components/schemas/CheckUserSubscriptionResponse +class Response extends TwitchData: + + ## A list that contains a single object with information about the user’s subscription. + @export var data: Array[TwitchUserSubscription]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserSubscription]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserSubscription.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_check_user_subscription.gd.uid b/addons/twitcher/generated/twitch_check_user_subscription.gd.uid new file mode 100644 index 00000000..ac086b1c --- /dev/null +++ b/addons/twitcher/generated/twitch_check_user_subscription.gd.uid @@ -0,0 +1 @@ +uid://bl18pfscbkvum diff --git a/addons/twitcher/generated/twitch_cheermote.gd b/addons/twitcher/generated/twitch_cheermote.gd new file mode 100644 index 00000000..a70bfa2b --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote.gd @@ -0,0 +1,162 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Cheermote +class_name TwitchCheermote + +## The name portion of the Cheermote string that you use in chat to cheer Bits. The full Cheermote string is the concatenation of {prefix} + {number of Bits}. For example, if the prefix is “Cheer” and you want to cheer 100 Bits, the full Cheermote string is Cheer100\. When the Cheermote string is entered in chat, Twitch converts it to the image associated with the Bits tier that was cheered. +@export var prefix: String: + set(val): + prefix = val + track_data(&"prefix", val) + +## A list of tier levels that the Cheermote supports. Each tier identifies the range of Bits that you can cheer at that tier level and an image that graphically identifies the tier level. +@export var tiers: Array[Tiers]: + set(val): + tiers = val + track_data(&"tiers", val) + +## The type of Cheermote. Possible values are: +## +## * global\_first\_party — A Twitch-defined Cheermote that is shown in the Bits card. +## * global\_third\_party — A Twitch-defined Cheermote that is not shown in the Bits card. +## * channel\_custom — A broadcaster-defined Cheermote. +## * display\_only — Do not use; for internal use only. +## * sponsored — A sponsor-defined Cheermote. When used, the sponsor adds additional Bits to the amount that the user cheered. For example, if the user cheered Terminator100, the broadcaster might receive 110 Bits, which includes the sponsor's 10 Bits contribution. +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The order that the Cheermotes are shown in the Bits card. The numbers may not be consecutive. For example, the numbers may jump from 1 to 7 to 13\. The order numbers are unique within a Cheermote type (for example, global\_first\_party) but may not be unique amongst all Cheermotes in the response. +@export var order: int: + set(val): + order = val + track_data(&"order", val) + +## The date and time, in RFC3339 format, when this Cheermote was last updated. +@export var last_updated: String: + set(val): + last_updated = val + track_data(&"last_updated", val) + +## A Boolean value that indicates whether this Cheermote provides a charitable contribution match during charity campaigns. +@export var is_charitable: bool: + set(val): + is_charitable = val + track_data(&"is_charitable", val) + + + +## Constructor with all required fields. +static func create(_prefix: String, _tiers: Array[Tiers], _type: String, _order: int, _last_updated: String, _is_charitable: bool) -> TwitchCheermote: + var twitch_cheermote: TwitchCheermote = TwitchCheermote.new() + twitch_cheermote.prefix = _prefix + twitch_cheermote.tiers = _tiers + twitch_cheermote.type = _type + twitch_cheermote.order = _order + twitch_cheermote.last_updated = _last_updated + twitch_cheermote.is_charitable = _is_charitable + return twitch_cheermote + + +static func from_json(d: Dictionary) -> TwitchCheermote: + var result: TwitchCheermote = TwitchCheermote.new() + if d.get("prefix", null) != null: + result.prefix = d["prefix"] + if d.get("tiers", null) != null: + for value in d["tiers"]: + result.tiers.append(Tiers.from_json(value)) + if d.get("type", null) != null: + result.type = d["type"] + if d.get("order", null) != null: + result.order = d["order"] + if d.get("last_updated", null) != null: + result.last_updated = d["last_updated"] + if d.get("is_charitable", null) != null: + result.is_charitable = d["is_charitable"] + return result + + + +## A list of tier levels that the Cheermote supports. Each tier identifies the range of Bits that you can cheer at that tier level and an image that graphically identifies the tier level. +## #/components/schemas/Cheermote/Tiers +class Tiers extends TwitchData: + + ## The minimum number of Bits that you must cheer at this tier level. The maximum number of Bits that you can cheer at this level is determined by the required minimum Bits of the next tier level minus 1\. For example, if `min_bits` is 1 and `min_bits` for the next tier is 100, the Bits range for this tier level is 1 through 99\. The minimum Bits value of the last tier is the maximum number of Bits you can cheer using this Cheermote. For example, 10000. + @export var min_bits: int: + set(val): + min_bits = val + track_data(&"min_bits", val) + + ## The tier level. Possible tiers are: + ## + ## * 1 + ## * 100 + ## * 500 + ## * 1000 + ## * 5000 + ## * 10000 + ## * 100000 + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The hex code of the color associated with this tier level (for example, #979797). + @export var color: String: + set(val): + color = val + track_data(&"color", val) + + ## + @export var images: TwitchCheermoteImages: + set(val): + images = val + track_data(&"images", val) + + ## A Boolean value that determines whether users can cheer at this tier level. + @export var can_cheer: bool: + set(val): + can_cheer = val + track_data(&"can_cheer", val) + + ## A Boolean value that determines whether this tier level is shown in the Bits card. Is **true** if this tier level is shown in the Bits card. + @export var show_in_bits_card: bool: + set(val): + show_in_bits_card = val + track_data(&"show_in_bits_card", val) + + + + ## Constructor with all required fields. + static func create(_min_bits: int, _id: String, _color: String, _images: TwitchCheermoteImages, _can_cheer: bool, _show_in_bits_card: bool) -> Tiers: + var tiers: Tiers = Tiers.new() + tiers.min_bits = _min_bits + tiers.id = _id + tiers.color = _color + tiers.images = _images + tiers.can_cheer = _can_cheer + tiers.show_in_bits_card = _show_in_bits_card + return tiers + + + static func from_json(d: Dictionary) -> Tiers: + var result: Tiers = Tiers.new() + if d.get("min_bits", null) != null: + result.min_bits = d["min_bits"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("color", null) != null: + result.color = d["color"] + if d.get("images", null) != null: + result.images = TwitchCheermoteImages.from_json(d["images"]) + if d.get("can_cheer", null) != null: + result.can_cheer = d["can_cheer"] + if d.get("show_in_bits_card", null) != null: + result.show_in_bits_card = d["show_in_bits_card"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_cheermote.gd.uid b/addons/twitcher/generated/twitch_cheermote.gd.uid new file mode 100644 index 00000000..f8c6dfb0 --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote.gd.uid @@ -0,0 +1 @@ +uid://b85p7sst7537c diff --git a/addons/twitcher/generated/twitch_cheermote_image_format.gd b/addons/twitcher/generated/twitch_cheermote_image_format.gd new file mode 100644 index 00000000..eb63bd40 --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_image_format.gd @@ -0,0 +1,60 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CheermoteImageFormat +class_name TwitchCheermoteImageFormat + +## +@export var _1: String: + set(val): + _1 = val + track_data(&"_1", val) + +## +@export var _2: String: + set(val): + _2 = val + track_data(&"_2", val) + +## +@export var _3: String: + set(val): + _3 = val + track_data(&"_3", val) + +## +@export var _4: String: + set(val): + _4 = val + track_data(&"_4", val) + +## +@export var _1_5: String: + set(val): + _1_5 = val + track_data(&"_1_5", val) + + + +## Constructor with all required fields. +static func create() -> TwitchCheermoteImageFormat: + var twitch_cheermote_image_format: TwitchCheermoteImageFormat = TwitchCheermoteImageFormat.new() + return twitch_cheermote_image_format + + +static func from_json(d: Dictionary) -> TwitchCheermoteImageFormat: + var result: TwitchCheermoteImageFormat = TwitchCheermoteImageFormat.new() + if d.get("_1", null) != null: + result._1 = d["_1"] + if d.get("_2", null) != null: + result._2 = d["_2"] + if d.get("_3", null) != null: + result._3 = d["_3"] + if d.get("_4", null) != null: + result._4 = d["_4"] + if d.get("_1_5", null) != null: + result._1_5 = d["_1_5"] + return result diff --git a/addons/twitcher/generated/twitch_cheermote_image_format.gd.uid b/addons/twitcher/generated/twitch_cheermote_image_format.gd.uid new file mode 100644 index 00000000..c0a2d2a1 --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_image_format.gd.uid @@ -0,0 +1 @@ +uid://csud7awisckb0 diff --git a/addons/twitcher/generated/twitch_cheermote_image_theme.gd b/addons/twitcher/generated/twitch_cheermote_image_theme.gd new file mode 100644 index 00000000..ec572c3c --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_image_theme.gd @@ -0,0 +1,36 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CheermoteImageTheme +class_name TwitchCheermoteImageTheme + +## +@export var animated_format: TwitchCheermoteImageFormat: + set(val): + animated_format = val + track_data(&"animated_format", val) + +## +@export var static_format: TwitchCheermoteImageFormat: + set(val): + static_format = val + track_data(&"static_format", val) + + + +## Constructor with all required fields. +static func create() -> TwitchCheermoteImageTheme: + var twitch_cheermote_image_theme: TwitchCheermoteImageTheme = TwitchCheermoteImageTheme.new() + return twitch_cheermote_image_theme + + +static func from_json(d: Dictionary) -> TwitchCheermoteImageTheme: + var result: TwitchCheermoteImageTheme = TwitchCheermoteImageTheme.new() + if d.get("animated_format", null) != null: + result.animated_format = TwitchCheermoteImageFormat.from_json(d["animated_format"]) + if d.get("static_format", null) != null: + result.static_format = TwitchCheermoteImageFormat.from_json(d["static_format"]) + return result diff --git a/addons/twitcher/generated/twitch_cheermote_image_theme.gd.uid b/addons/twitcher/generated/twitch_cheermote_image_theme.gd.uid new file mode 100644 index 00000000..8804aa9f --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_image_theme.gd.uid @@ -0,0 +1 @@ +uid://c0ndet8biyyie diff --git a/addons/twitcher/generated/twitch_cheermote_images.gd b/addons/twitcher/generated/twitch_cheermote_images.gd new file mode 100644 index 00000000..c3ffbe53 --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_images.gd @@ -0,0 +1,36 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CheermoteImages +class_name TwitchCheermoteImages + +## +@export var light: TwitchCheermoteImageTheme: + set(val): + light = val + track_data(&"light", val) + +## +@export var dark: TwitchCheermoteImageTheme: + set(val): + dark = val + track_data(&"dark", val) + + + +## Constructor with all required fields. +static func create() -> TwitchCheermoteImages: + var twitch_cheermote_images: TwitchCheermoteImages = TwitchCheermoteImages.new() + return twitch_cheermote_images + + +static func from_json(d: Dictionary) -> TwitchCheermoteImages: + var result: TwitchCheermoteImages = TwitchCheermoteImages.new() + if d.get("light", null) != null: + result.light = TwitchCheermoteImageTheme.from_json(d["light"]) + if d.get("dark", null) != null: + result.dark = TwitchCheermoteImageTheme.from_json(d["dark"]) + return result diff --git a/addons/twitcher/generated/twitch_cheermote_images.gd.uid b/addons/twitcher/generated/twitch_cheermote_images.gd.uid new file mode 100644 index 00000000..560cd8cc --- /dev/null +++ b/addons/twitcher/generated/twitch_cheermote_images.gd.uid @@ -0,0 +1 @@ +uid://cv2utje686064 diff --git a/addons/twitcher/generated/twitch_clip.gd b/addons/twitcher/generated/twitch_clip.gd new file mode 100644 index 00000000..c859b7b6 --- /dev/null +++ b/addons/twitcher/generated/twitch_clip.gd @@ -0,0 +1,175 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Clip +class_name TwitchClip + +## An ID that uniquely identifies the clip. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## A URL to the clip. +@export var url: String: + set(val): + url = val + track_data(&"url", val) + +## A URL that you can use in an iframe to embed the clip (see [Embedding Video and Clips](https://dev.twitch.tv/docs/embed/video-and-clips/)). +@export var embed_url: String: + set(val): + embed_url = val + track_data(&"embed_url", val) + +## An ID that identifies the broadcaster that the video was clipped from. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## An ID that identifies the user that created the clip. +@export var creator_id: String: + set(val): + creator_id = val + track_data(&"creator_id", val) + +## The user’s display name. +@export var creator_name: String: + set(val): + creator_name = val + track_data(&"creator_name", val) + +## An ID that identifies the video that the clip came from. This field contains an empty string if the video is not available. +@export var video_id: String: + set(val): + video_id = val + track_data(&"video_id", val) + +## The ID of the game that was being played when the clip was created. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The ISO 639-1 two-letter language code that the broadcaster broadcasts in. For example, _en_ for English. The value is _other_ if the broadcaster uses a language that Twitch doesn’t support. +@export var language: String: + set(val): + language = val + track_data(&"language", val) + +## The title of the clip. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The number of times the clip has been viewed. +@export var view_count: int: + set(val): + view_count = val + track_data(&"view_count", val) + +## The date and time of when the clip was created. The date and time is in RFC3339 format. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## A URL to a thumbnail image of the clip. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## The length of the clip, in seconds. Precision is 0.1. +@export var duration: float: + set(val): + duration = val + track_data(&"duration", val) + +## The zero-based offset, in seconds, to where the clip starts in the video (VOD). Is **null** if the video is not available or hasn’t been created yet from the live stream (see `video_id`). +## +## Note that there’s a delay between when a clip is created during a broadcast and when the offset is set. During the delay period, `vod_offset` is **null**. The delay is indeterminant but is typically minutes long. +@export var vod_offset: int: + set(val): + vod_offset = val + track_data(&"vod_offset", val) + +## A Boolean value that indicates if the clip is featured or not. +@export var is_featured: bool: + set(val): + is_featured = val + track_data(&"is_featured", val) + + + +## Constructor with all required fields. +static func create(_id: String, _url: String, _embed_url: String, _broadcaster_id: String, _broadcaster_name: String, _creator_id: String, _creator_name: String, _video_id: String, _game_id: String, _language: String, _title: String, _view_count: int, _created_at: String, _thumbnail_url: String, _duration: float, _vod_offset: int, _is_featured: bool) -> TwitchClip: + var twitch_clip: TwitchClip = TwitchClip.new() + twitch_clip.id = _id + twitch_clip.url = _url + twitch_clip.embed_url = _embed_url + twitch_clip.broadcaster_id = _broadcaster_id + twitch_clip.broadcaster_name = _broadcaster_name + twitch_clip.creator_id = _creator_id + twitch_clip.creator_name = _creator_name + twitch_clip.video_id = _video_id + twitch_clip.game_id = _game_id + twitch_clip.language = _language + twitch_clip.title = _title + twitch_clip.view_count = _view_count + twitch_clip.created_at = _created_at + twitch_clip.thumbnail_url = _thumbnail_url + twitch_clip.duration = _duration + twitch_clip.vod_offset = _vod_offset + twitch_clip.is_featured = _is_featured + return twitch_clip + + +static func from_json(d: Dictionary) -> TwitchClip: + var result: TwitchClip = TwitchClip.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("url", null) != null: + result.url = d["url"] + if d.get("embed_url", null) != null: + result.embed_url = d["embed_url"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("creator_id", null) != null: + result.creator_id = d["creator_id"] + if d.get("creator_name", null) != null: + result.creator_name = d["creator_name"] + if d.get("video_id", null) != null: + result.video_id = d["video_id"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("language", null) != null: + result.language = d["language"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("view_count", null) != null: + result.view_count = d["view_count"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("vod_offset", null) != null: + result.vod_offset = d["vod_offset"] + if d.get("is_featured", null) != null: + result.is_featured = d["is_featured"] + return result diff --git a/addons/twitcher/generated/twitch_clip.gd.uid b/addons/twitcher/generated/twitch_clip.gd.uid new file mode 100644 index 00000000..06bd4b45 --- /dev/null +++ b/addons/twitcher/generated/twitch_clip.gd.uid @@ -0,0 +1 @@ +uid://xnhce7fula3h diff --git a/addons/twitcher/generated/twitch_content_classification_label.gd b/addons/twitcher/generated/twitch_content_classification_label.gd new file mode 100644 index 00000000..29fdde23 --- /dev/null +++ b/addons/twitcher/generated/twitch_content_classification_label.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ContentClassificationLabel +class_name TwitchContentClassificationLabel + +## Unique identifier for the CCL. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## Localized description of the CCL. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + +## Localized name of the CCL. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + + + +## Constructor with all required fields. +static func create(_id: String, _description: String, _name: String) -> TwitchContentClassificationLabel: + var twitch_content_classification_label: TwitchContentClassificationLabel = TwitchContentClassificationLabel.new() + twitch_content_classification_label.id = _id + twitch_content_classification_label.description = _description + twitch_content_classification_label.name = _name + return twitch_content_classification_label + + +static func from_json(d: Dictionary) -> TwitchContentClassificationLabel: + var result: TwitchContentClassificationLabel = TwitchContentClassificationLabel.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("name", null) != null: + result.name = d["name"] + return result diff --git a/addons/twitcher/generated/twitch_content_classification_label.gd.uid b/addons/twitcher/generated/twitch_content_classification_label.gd.uid new file mode 100644 index 00000000..aba46622 --- /dev/null +++ b/addons/twitcher/generated/twitch_content_classification_label.gd.uid @@ -0,0 +1 @@ +uid://bdpfg15y6hg6t diff --git a/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd b/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd new file mode 100644 index 00000000..092bddd3 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd @@ -0,0 +1,203 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateChannelStreamScheduleSegment + + + +## +## #/components/schemas/CreateChannelStreamScheduleSegmentBody +class Body extends TwitchData: + + ## The date and time that the broadcast segment starts. Specify the date and time in RFC3339 format (for example, 2021-07-01T18:00:00Z). + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## The time zone where the broadcast takes place. Specify the time zone using [IANA time zone database](https://www.iana.org/time-zones) format (for example, America/New\_York). + @export var timezone: String: + set(val): + timezone = val + track_data(&"timezone", val) + + ## The length of time, in minutes, that the broadcast is scheduled to run. The duration must be in the range 30 through 1380 (23 hours). + @export var duration: String: + set(val): + duration = val + track_data(&"duration", val) + + ## A Boolean value that determines whether the broadcast recurs weekly. Is **true** if the broadcast recurs weekly. Only partners and affiliates may add non-recurring broadcasts. + @export var is_recurring: bool: + set(val): + is_recurring = val + track_data(&"is_recurring", val) + + ## The ID of the category that best represents the broadcast’s content. To get the category ID, use the [Search Categories](https://dev.twitch.tv/docs/api/reference#search-categories) endpoint. + @export var category_id: String: + set(val): + category_id = val + track_data(&"category_id", val) + + ## The broadcast’s title. The title may contain a maximum of 140 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + + + ## Constructor with all required fields. + static func create(_start_time: String, _timezone: String, _duration: String) -> Body: + var body: Body = Body.new() + body.start_time = _start_time + body.timezone = _timezone + body.duration = _duration + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("timezone", null) != null: + result.timezone = d["timezone"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("is_recurring", null) != null: + result.is_recurring = d["is_recurring"] + if d.get("category_id", null) != null: + result.category_id = d["category_id"] + if d.get("title", null) != null: + result.title = d["title"] + return result + + + +## +## #/components/schemas/CreateChannelStreamScheduleSegmentResponse +class Response extends TwitchData: + + ## The broadcaster’s streaming scheduled. + @export var data: ResponseData: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: ResponseData) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + result.data = ResponseData.from_json(d["data"]) + return result + + + +## The broadcaster’s streaming scheduled. +## #/components/schemas/CreateChannelStreamScheduleSegmentResponse/Data +class ResponseData extends TwitchData: + + ## A list that contains the single broadcast segment that you added. + @export var segments: Array[TwitchChannelStreamScheduleSegment]: + set(val): + segments = val + track_data(&"segments", val) + + ## The ID of the broadcaster that owns the broadcast schedule. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster’s display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## The broadcaster’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. + @export var vacation: ResponseVacation: + set(val): + vacation = val + track_data(&"vacation", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_segments: Array[TwitchChannelStreamScheduleSegment], _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _vacation: ResponseVacation) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.segments = _segments + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_name = _broadcaster_name + response_data.broadcaster_login = _broadcaster_login + response_data.vacation = _vacation + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("segments", null) != null: + for value in d["segments"]: + result.segments.append(TwitchChannelStreamScheduleSegment.from_json(value)) + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("vacation", null) != null: + result.vacation = ResponseVacation.from_json(d["vacation"]) + return result + + + +## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. +## #/components/schemas/CreateChannelStreamScheduleSegmentResponse/Data/Vacation +class ResponseVacation extends TwitchData: + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation starts. + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation ends. + @export var end_time: String: + set(val): + end_time = val + track_data(&"end_time", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_start_time: String, _end_time: String) -> ResponseVacation: + var response_vacation: ResponseVacation = ResponseVacation.new() + response_vacation.start_time = _start_time + response_vacation.end_time = _end_time + return response_vacation + + + static func from_json(d: Dictionary) -> ResponseVacation: + var result: ResponseVacation = ResponseVacation.new() + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("end_time", null) != null: + result.end_time = d["end_time"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd.uid b/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd.uid new file mode 100644 index 00000000..c089ac16 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_channel_stream_schedule_segment.gd.uid @@ -0,0 +1 @@ +uid://dirdmoagnwksy diff --git a/addons/twitcher/generated/twitch_create_clip.gd b/addons/twitcher/generated/twitch_create_clip.gd new file mode 100644 index 00000000..6d4ec6f5 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_clip.gd @@ -0,0 +1,99 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateClip + + + +## +## #/components/schemas/CreateClipResponse +class Response extends TwitchData: + + ## + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## +## #/components/schemas/CreateClipResponse/Data +class ResponseData extends TwitchData: + + ## A URL that you can use to edit the clip’s title, identify the part of the clip to publish, and publish the clip. [Learn More](https://help.twitch.tv/s/article/how-to-use-clips) + ## + ## The URL is valid for up to 24 hours or until the clip is published, whichever comes first. + @export var edit_url: String: + set(val): + edit_url = val + track_data(&"edit_url", val) + + ## An ID that uniquely identifies the clip. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_edit_url: String, _id: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.edit_url = _edit_url + response_data.id = _id + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("edit_url", null) != null: + result.edit_url = d["edit_url"] + if d.get("id", null) != null: + result.id = d["id"] + return result + + + +## All optional parameters for TwitchAPI.create_clip +## #/components/schemas/CreateClipOpt +class Opt extends TwitchData: + + ## A Boolean value that determines whether the API captures the clip at the moment the viewer requests it or after a delay. If **false** (default), Twitch captures the clip at the moment the viewer requests it (this is the same clip experience as the Twitch UX). If **true**, Twitch adds a delay before capturing the clip (this basically shifts the capture window to the right slightly). + @export var has_delay: bool: + set(val): + has_delay = val + track_data(&"has_delay", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("has_delay", null) != null: + result.has_delay = d["has_delay"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_clip.gd.uid b/addons/twitcher/generated/twitch_create_clip.gd.uid new file mode 100644 index 00000000..e0857c61 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_clip.gd.uid @@ -0,0 +1 @@ +uid://dl4qg2h6hfagg diff --git a/addons/twitcher/generated/twitch_create_conduits.gd b/addons/twitcher/generated/twitch_create_conduits.gd new file mode 100644 index 00000000..420d5aec --- /dev/null +++ b/addons/twitcher/generated/twitch_create_conduits.gd @@ -0,0 +1,98 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateConduits + + + +## +## #/components/schemas/CreateConduitsBody +class Body extends TwitchData: + + ## The number of shards to create for this conduit. + @export var shard_count: int: + set(val): + shard_count = val + track_data(&"shard_count", val) + + + + ## Constructor with all required fields. + static func create(_shard_count: int) -> Body: + var body: Body = Body.new() + body.shard_count = _shard_count + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("shard_count", null) != null: + result.shard_count = d["shard_count"] + return result + + + +## +## #/components/schemas/CreateConduitsResponse +class Response extends TwitchData: + + ## List of information about the client’s conduits. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## List of information about the client’s conduits. +## #/components/schemas/CreateConduitsResponse/Data +class ResponseData extends TwitchData: + + ## Conduit ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## Number of shards created for this conduit. + @export var shard_count: int: + set(val): + shard_count = val + track_data(&"shard_count", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _shard_count: int) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.shard_count = _shard_count + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("shard_count", null) != null: + result.shard_count = d["shard_count"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_conduits.gd.uid b/addons/twitcher/generated/twitch_create_conduits.gd.uid new file mode 100644 index 00000000..40c25542 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_conduits.gd.uid @@ -0,0 +1 @@ +uid://bons7t6r3bd0i diff --git a/addons/twitcher/generated/twitch_create_custom_rewards.gd b/addons/twitcher/generated/twitch_create_custom_rewards.gd new file mode 100644 index 00000000..9c71f1dc --- /dev/null +++ b/addons/twitcher/generated/twitch_create_custom_rewards.gd @@ -0,0 +1,159 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateCustomRewards + + + +## +## #/components/schemas/CreateCustomRewardsBody +class Body extends TwitchData: + + ## The custom reward’s title. The title may contain a maximum of 45 characters and it must be unique amongst all of the broadcaster’s custom rewards. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The cost of the reward, in Channel Points. The minimum is 1 point. + @export var cost: int: + set(val): + cost = val + track_data(&"cost", val) + + ## The prompt shown to the viewer when they redeem the reward. Specify a prompt if `is_user_input_required` is **true**. The prompt is limited to a maximum of 200 characters. + @export var prompt: String: + set(val): + prompt = val + track_data(&"prompt", val) + + ## A Boolean value that determines whether the reward is enabled. Viewers see only enabled rewards. The default is **true**. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + ## The background color to use for the reward. Specify the color using Hex format (for example, #9147FF). + @export var background_color: String: + set(val): + background_color = val + track_data(&"background_color", val) + + ## A Boolean value that determines whether the user needs to enter information when redeeming the reward. See the `prompt` field. The default is **false**. + @export var is_user_input_required: bool: + set(val): + is_user_input_required = val + track_data(&"is_user_input_required", val) + + ## A Boolean value that determines whether to limit the maximum number of redemptions allowed per live stream (see the `max_per_stream` field). The default is **false**. + @export var is_max_per_stream_enabled: bool: + set(val): + is_max_per_stream_enabled = val + track_data(&"is_max_per_stream_enabled", val) + + ## The maximum number of redemptions allowed per live stream. Applied only if `is_max_per_stream_enabled` is **true**. The minimum value is 1. + @export var max_per_stream: int: + set(val): + max_per_stream = val + track_data(&"max_per_stream", val) + + ## A Boolean value that determines whether to limit the maximum number of redemptions allowed per user per stream (see the `max_per_user_per_stream` field). The default is **false**. + @export var is_max_per_user_per_stream_enabled: bool: + set(val): + is_max_per_user_per_stream_enabled = val + track_data(&"is_max_per_user_per_stream_enabled", val) + + ## The maximum number of redemptions allowed per user per stream. Applied only if `is_max_per_user_per_stream_enabled` is **true**. The minimum value is 1. + @export var max_per_user_per_stream: int: + set(val): + max_per_user_per_stream = val + track_data(&"max_per_user_per_stream", val) + + ## A Boolean value that determines whether to apply a cooldown period between redemptions (see the `global_cooldown_seconds` field for the duration of the cooldown period). The default is **false**. + @export var is_global_cooldown_enabled: bool: + set(val): + is_global_cooldown_enabled = val + track_data(&"is_global_cooldown_enabled", val) + + ## The cooldown period, in seconds. Applied only if the `is_global_cooldown_enabled` field is **true**. The minimum value is 1; however, the minimum value is 60 for it to be shown in the Twitch UX. + @export var global_cooldown_seconds: int: + set(val): + global_cooldown_seconds = val + track_data(&"global_cooldown_seconds", val) + + ## A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed. If **false**, status is set to UNFULFILLED and follows the normal request queue process. The default is **false**. + @export var should_redemptions_skip_request_queue: bool: + set(val): + should_redemptions_skip_request_queue = val + track_data(&"should_redemptions_skip_request_queue", val) + + + + ## Constructor with all required fields. + static func create(_title: String, _cost: int) -> Body: + var body: Body = Body.new() + body.title = _title + body.cost = _cost + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("title", null) != null: + result.title = d["title"] + if d.get("cost", null) != null: + result.cost = d["cost"] + if d.get("prompt", null) != null: + result.prompt = d["prompt"] + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("background_color", null) != null: + result.background_color = d["background_color"] + if d.get("is_user_input_required", null) != null: + result.is_user_input_required = d["is_user_input_required"] + if d.get("is_max_per_stream_enabled", null) != null: + result.is_max_per_stream_enabled = d["is_max_per_stream_enabled"] + if d.get("max_per_stream", null) != null: + result.max_per_stream = d["max_per_stream"] + if d.get("is_max_per_user_per_stream_enabled", null) != null: + result.is_max_per_user_per_stream_enabled = d["is_max_per_user_per_stream_enabled"] + if d.get("max_per_user_per_stream", null) != null: + result.max_per_user_per_stream = d["max_per_user_per_stream"] + if d.get("is_global_cooldown_enabled", null) != null: + result.is_global_cooldown_enabled = d["is_global_cooldown_enabled"] + if d.get("global_cooldown_seconds", null) != null: + result.global_cooldown_seconds = d["global_cooldown_seconds"] + if d.get("should_redemptions_skip_request_queue", null) != null: + result.should_redemptions_skip_request_queue = d["should_redemptions_skip_request_queue"] + return result + + + +## +## #/components/schemas/CreateCustomRewardsResponse +class Response extends TwitchData: + + ## A list that contains the single custom reward you created. + @export var data: Array[TwitchCustomReward]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCustomReward]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCustomReward.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_custom_rewards.gd.uid b/addons/twitcher/generated/twitch_create_custom_rewards.gd.uid new file mode 100644 index 00000000..9263e0e3 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_custom_rewards.gd.uid @@ -0,0 +1 @@ +uid://cv6f7hpvjsm24 diff --git a/addons/twitcher/generated/twitch_create_event_sub_subscription.gd b/addons/twitcher/generated/twitch_create_event_sub_subscription.gd new file mode 100644 index 00000000..99a7f57e --- /dev/null +++ b/addons/twitcher/generated/twitch_create_event_sub_subscription.gd @@ -0,0 +1,181 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateEventSubSubscription + + + +## +## #/components/schemas/CreateEventSubSubscriptionBody +class Body extends TwitchData: + + ## The type of subscription to create. For a list of subscriptions that you can create, see [Subscription Types](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#subscription-types). Set this field to the value in the **Name** column of the Subscription Types table. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The version number that identifies the definition of the subscription type that you want the response to use. + @export var version: String: + set(val): + version = val + track_data(&"version", val) + + ## A JSON object that contains the parameter values that are specific to the specified subscription type. For the object’s required and optional fields, see the subscription type’s documentation. + @export var condition: Dictionary: + set(val): + condition = val + track_data(&"condition", val) + + ## The transport details that you want Twitch to use when sending you notifications. + @export var transport: BodyTransport: + set(val): + transport = val + track_data(&"transport", val) + + + + ## Constructor with all required fields. + static func create(_type: String, _version: String, _condition: Dictionary, _transport: BodyTransport) -> Body: + var body: Body = Body.new() + body.type = _type + body.version = _version + body.condition = _condition + body.transport = _transport + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("type", null) != null: + result.type = d["type"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("condition", null) != null: + result.condition = d["condition"] + if d.get("transport", null) != null: + result.transport = BodyTransport.from_json(d["transport"]) + return result + + + +## The transport details that you want Twitch to use when sending you notifications. +## #/components/schemas/CreateEventSubSubscriptionBody/Transport +class BodyTransport extends TwitchData: + + ## The transport method. Possible values are: + ## + ## * webhook + ## * websocket + ## * conduit + @export var method: String: + set(val): + method = val + track_data(&"method", val) + + ## The callback URL where the notifications are sent. The URL must use the HTTPS protocol and port 443\. See [Processing an event](https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event). Specify this field only if `method` is set to **webhook**. + ## + ## **NOTE**: Redirects are not followed. + @export var callback: String: + set(val): + callback = val + track_data(&"callback", val) + + ## The secret used to verify the signature. The secret must be an ASCII string that’s a minimum of 10 characters long and a maximum of 100 characters long. For information about how the secret is used, see [Verifying the event message](https://dev.twitch.tv/docs/eventsub/handling-webhook-events#verifying-the-event-message). Specify this field only if `method` is set to **webhook**. + @export var secret: String: + set(val): + secret = val + track_data(&"secret", val) + + ## An ID that identifies the WebSocket to send notifications to. When you connect to EventSub using WebSockets, the server returns the ID in the Welcome message. Specify this field only if `method` is set to **websocket**. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + ## An ID that identifies the conduit to send notifications to. When you create a conduit, the server returns the conduit ID. Specify this field only if `method` is set to **conduit**. + @export var conduit_id: String: + set(val): + conduit_id = val + track_data(&"conduit_id", val) + + + + ## Constructor with all required fields. + static func create(_method: String) -> BodyTransport: + var body_transport: BodyTransport = BodyTransport.new() + body_transport.method = _method + return body_transport + + + static func from_json(d: Dictionary) -> BodyTransport: + var result: BodyTransport = BodyTransport.new() + if d.get("method", null) != null: + result.method = d["method"] + if d.get("callback", null) != null: + result.callback = d["callback"] + if d.get("secret", null) != null: + result.secret = d["secret"] + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + if d.get("conduit_id", null) != null: + result.conduit_id = d["conduit_id"] + return result + + + +## +## #/components/schemas/CreateEventSubSubscriptionResponse +class Response extends TwitchData: + + ## A list that contains the single subscription that you created. + @export var data: Array[TwitchEventSubSubscription]: + set(val): + data = val + track_data(&"data", val) + + ## The total number of subscriptions you’ve created. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The sum of all of your subscription costs. [Learn More](https://dev.twitch.tv/docs/eventsub/manage-subscriptions/#subscription-limits) + @export var total_cost: int: + set(val): + total_cost = val + track_data(&"total_cost", val) + + ## The maximum total cost that you’re allowed to incur for all subscriptions you create. + @export var max_total_cost: int: + set(val): + max_total_cost = val + track_data(&"max_total_cost", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchEventSubSubscription], _total: int, _total_cost: int, _max_total_cost: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.total = _total + response.total_cost = _total_cost + response.max_total_cost = _max_total_cost + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchEventSubSubscription.from_json(value)) + if d.get("total", null) != null: + result.total = d["total"] + if d.get("total_cost", null) != null: + result.total_cost = d["total_cost"] + if d.get("max_total_cost", null) != null: + result.max_total_cost = d["max_total_cost"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_event_sub_subscription.gd.uid b/addons/twitcher/generated/twitch_create_event_sub_subscription.gd.uid new file mode 100644 index 00000000..5a5162c1 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_event_sub_subscription.gd.uid @@ -0,0 +1 @@ +uid://o5jsuop8s3fq diff --git a/addons/twitcher/generated/twitch_create_extension_secret.gd b/addons/twitcher/generated/twitch_create_extension_secret.gd new file mode 100644 index 00000000..ab8e4a93 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_extension_secret.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateExtensionSecret + + + +## +## #/components/schemas/CreateExtensionSecretResponse +class Response extends TwitchData: + + ## A list that contains the newly added secrets. + @export var data: Array[TwitchExtensionSecret]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionSecret]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionSecret.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.create_extension_secret +## #/components/schemas/CreateExtensionSecretOpt +class Opt extends TwitchData: + + ## The amount of time, in seconds, to delay activating the secret. The delay should provide enough time for instances of the extension to gracefully switch over to the new secret. The minimum delay is 300 seconds (5 minutes). The default is 300 seconds. + @export var delay: int: + set(val): + delay = val + track_data(&"delay", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("delay", null) != null: + result.delay = d["delay"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_extension_secret.gd.uid b/addons/twitcher/generated/twitch_create_extension_secret.gd.uid new file mode 100644 index 00000000..ed807fba --- /dev/null +++ b/addons/twitcher/generated/twitch_create_extension_secret.gd.uid @@ -0,0 +1 @@ +uid://cve0b0q7ilalj diff --git a/addons/twitcher/generated/twitch_create_guest_star_session.gd b/addons/twitcher/generated/twitch_create_guest_star_session.gd new file mode 100644 index 00000000..f747ea9c --- /dev/null +++ b/addons/twitcher/generated/twitch_create_guest_star_session.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateGuestStarSession + + + +## +## #/components/schemas/CreateGuestStarSessionResponse +class Response extends TwitchData: + + ## Summary of the session details. + @export var data: Array[TwitchGuestStarSession]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGuestStarSession]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGuestStarSession.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_guest_star_session.gd.uid b/addons/twitcher/generated/twitch_create_guest_star_session.gd.uid new file mode 100644 index 00000000..78bdacf8 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_guest_star_session.gd.uid @@ -0,0 +1 @@ +uid://xisxuptqxomh diff --git a/addons/twitcher/generated/twitch_create_poll.gd b/addons/twitcher/generated/twitch_create_poll.gd new file mode 100644 index 00000000..23893b92 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_poll.gd @@ -0,0 +1,133 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreatePoll + + + +## +## #/components/schemas/CreatePollBody +class Body extends TwitchData: + + ## The ID of the broadcaster that’s running the poll. This ID must match the user ID in the user access token. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The question that viewers will vote on. For example, _What game should I play next?_ The question may contain a maximum of 60 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## A list of choices that viewers may choose from. The list must contain a minimum of 2 choices and up to a maximum of 5 choices. + @export var choices: Array[BodyChoices]: + set(val): + choices = val + track_data(&"choices", val) + + ## The length of time (in seconds) that the poll will run for. The minimum is 15 seconds and the maximum is 1800 seconds (30 minutes). + @export var duration: int: + set(val): + duration = val + track_data(&"duration", val) + + ## A Boolean value that indicates whether viewers may cast additional votes using Channel Points. If **true**, the viewer may cast more than one vote but each additional vote costs the number of Channel Points specified in `channel_points_per_vote`. The default is **false** (viewers may cast only one vote). For information about Channel Points, see [Channel Points Guide](https://help.twitch.tv/s/article/channel-points-guide). + @export var channel_points_voting_enabled: bool: + set(val): + channel_points_voting_enabled = val + track_data(&"channel_points_voting_enabled", val) + + ## The number of points that the viewer must spend to cast one additional vote. The minimum is 1 and the maximum is 1000000\. Set only if `ChannelPointsVotingEnabled` is **true**. + @export var channel_points_per_vote: int: + set(val): + channel_points_per_vote = val + track_data(&"channel_points_per_vote", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _title: String, _choices: Array[BodyChoices], _duration: int) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.title = _title + body.choices = _choices + body.duration = _duration + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("choices", null) != null: + for value in d["choices"]: + result.choices.append(BodyChoices.from_json(value)) + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("channel_points_voting_enabled", null) != null: + result.channel_points_voting_enabled = d["channel_points_voting_enabled"] + if d.get("channel_points_per_vote", null) != null: + result.channel_points_per_vote = d["channel_points_per_vote"] + return result + + + +## A list of choices that viewers may choose from. The list must contain a minimum of 2 choices and up to a maximum of 5 choices. +## #/components/schemas/CreatePollBody/Choices +class BodyChoices extends TwitchData: + + ## One of the choices the viewer may select. The choice may contain a maximum of 25 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + + + ## Constructor with all required fields. + static func create(_title: String) -> BodyChoices: + var body_choices: BodyChoices = BodyChoices.new() + body_choices.title = _title + return body_choices + + + static func from_json(d: Dictionary) -> BodyChoices: + var result: BodyChoices = BodyChoices.new() + if d.get("title", null) != null: + result.title = d["title"] + return result + + + +## +## #/components/schemas/CreatePollResponse +class Response extends TwitchData: + + ## A list that contains the single poll that you created. + @export var data: Array[TwitchPoll]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPoll]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPoll.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_poll.gd.uid b/addons/twitcher/generated/twitch_create_poll.gd.uid new file mode 100644 index 00000000..7d206cc5 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_poll.gd.uid @@ -0,0 +1 @@ +uid://cshwdnu6h67gu diff --git a/addons/twitcher/generated/twitch_create_prediction.gd b/addons/twitcher/generated/twitch_create_prediction.gd new file mode 100644 index 00000000..acd6eed3 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_prediction.gd @@ -0,0 +1,117 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreatePrediction + + + +## +## #/components/schemas/CreatePredictionBody +class Body extends TwitchData: + + ## The ID of the broadcaster that’s running the prediction. This ID must match the user ID in the user access token. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The question that the broadcaster is asking. For example, _Will I finish this entire pizza?_ The title is limited to a maximum of 45 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The list of possible outcomes that the viewers may choose from. The list must contain a minimum of 2 choices and up to a maximum of 10 choices. + @export var outcomes: Array[BodyOutcomes]: + set(val): + outcomes = val + track_data(&"outcomes", val) + + ## The length of time (in seconds) that the prediction will run for. The minimum is 30 seconds and the maximum is 1800 seconds (30 minutes). + @export var prediction_window: int: + set(val): + prediction_window = val + track_data(&"prediction_window", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _title: String, _outcomes: Array[BodyOutcomes], _prediction_window: int) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.title = _title + body.outcomes = _outcomes + body.prediction_window = _prediction_window + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("outcomes", null) != null: + for value in d["outcomes"]: + result.outcomes.append(BodyOutcomes.from_json(value)) + if d.get("prediction_window", null) != null: + result.prediction_window = d["prediction_window"] + return result + + + +## The list of possible outcomes that the viewers may choose from. The list must contain a minimum of 2 choices and up to a maximum of 10 choices. +## #/components/schemas/CreatePredictionBody/Outcomes +class BodyOutcomes extends TwitchData: + + ## The text of one of the outcomes that the viewer may select. The title is limited to a maximum of 25 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + + + ## Constructor with all required fields. + static func create(_title: String) -> BodyOutcomes: + var body_outcomes: BodyOutcomes = BodyOutcomes.new() + body_outcomes.title = _title + return body_outcomes + + + static func from_json(d: Dictionary) -> BodyOutcomes: + var result: BodyOutcomes = BodyOutcomes.new() + if d.get("title", null) != null: + result.title = d["title"] + return result + + + +## +## #/components/schemas/CreatePredictionResponse +class Response extends TwitchData: + + ## A list that contains the single prediction that you created. + @export var data: Array[TwitchPrediction]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPrediction]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPrediction.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_prediction.gd.uid b/addons/twitcher/generated/twitch_create_prediction.gd.uid new file mode 100644 index 00000000..440bc00b --- /dev/null +++ b/addons/twitcher/generated/twitch_create_prediction.gd.uid @@ -0,0 +1 @@ +uid://oas7j3xjgwex diff --git a/addons/twitcher/generated/twitch_create_stream_marker.gd b/addons/twitcher/generated/twitch_create_stream_marker.gd new file mode 100644 index 00000000..193e3e47 --- /dev/null +++ b/addons/twitcher/generated/twitch_create_stream_marker.gd @@ -0,0 +1,70 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchCreateStreamMarker + + + +## +## #/components/schemas/CreateStreamMarkerBody +class Body extends TwitchData: + + ## The ID of the broadcaster that’s streaming content. This ID must match the user ID in the access token or the user in the access token must be one of the broadcaster’s editors. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## A short description of the marker to help the user remember why they marked the location. The maximum length of the description is 140 characters. + @export var description: String: + set(val): + description = val + track_data(&"description", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String) -> Body: + var body: Body = Body.new() + body.user_id = _user_id + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("description", null) != null: + result.description = d["description"] + return result + + + +## +## #/components/schemas/CreateStreamMarkerResponse +class Response extends TwitchData: + + ## A list that contains the single marker that you added. + @export var data: Array[TwitchStreamMarkerCreated]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStreamMarkerCreated]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStreamMarkerCreated.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_create_stream_marker.gd.uid b/addons/twitcher/generated/twitch_create_stream_marker.gd.uid new file mode 100644 index 00000000..d78c9dfb --- /dev/null +++ b/addons/twitcher/generated/twitch_create_stream_marker.gd.uid @@ -0,0 +1 @@ +uid://6xerpyoforp2 diff --git a/addons/twitcher/generated/twitch_creator_goal.gd b/addons/twitcher/generated/twitch_creator_goal.gd new file mode 100644 index 00000000..204be14e --- /dev/null +++ b/addons/twitcher/generated/twitch_creator_goal.gd @@ -0,0 +1,115 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CreatorGoal +class_name TwitchCreatorGoal + +## An ID that identifies this goal. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the broadcaster that created the goal. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The type of goal. Possible values are: +## +## * follower — The goal is to increase followers. +## * subscription — The goal is to increase subscriptions. This type shows the net increase or decrease in tier points associated with the subscriptions. +## * subscription\_count — The goal is to increase subscriptions. This type shows the net increase or decrease in the number of subscriptions. +## * new\_subscription — The goal is to increase subscriptions. This type shows only the net increase in tier points associated with the subscriptions (it does not account for users that unsubscribed since the goal started). +## * new\_subscription\_count — The goal is to increase subscriptions. This type shows only the net increase in the number of subscriptions (it does not account for users that unsubscribed since the goal started). +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## A description of the goal. Is an empty string if not specified. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + +## The goal’s current value. +## +## The goal’s `type` determines how this value is increased or decreased. +## +## * If `type` is follower, this field is set to the broadcaster's current number of followers. This number increases with new followers and decreases when users unfollow the broadcaster. +## * If `type` is subscription, this field is increased and decreased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased or decreased by 2, not 1. +## * If `type` is subscription\_count, this field is increased by 1 for each new subscription and decreased by 1 for each user that unsubscribes. +## * If `type` is new\_subscription, this field is increased by the points value associated with the subscription tier. For example, if a tier-two subscription is worth 2 points, this field is increased by 2, not 1. +## * If `type` is new\_subscription\_count, this field is increased by 1 for each new subscription. +@export var current_amount: int: + set(val): + current_amount = val + track_data(&"current_amount", val) + +## The goal’s target value. For example, if the broadcaster has 200 followers before creating the goal, and their goal is to double that number, this field is set to 400. +@export var target_amount: int: + set(val): + target_amount = val + track_data(&"target_amount", val) + +## The UTC date and time (in RFC3339 format) that the broadcaster created the goal. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + + +## Constructor with all required fields. +static func create(_id: String, _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _type: String, _description: String, _current_amount: int, _target_amount: int, _created_at: String) -> TwitchCreatorGoal: + var twitch_creator_goal: TwitchCreatorGoal = TwitchCreatorGoal.new() + twitch_creator_goal.id = _id + twitch_creator_goal.broadcaster_id = _broadcaster_id + twitch_creator_goal.broadcaster_name = _broadcaster_name + twitch_creator_goal.broadcaster_login = _broadcaster_login + twitch_creator_goal.type = _type + twitch_creator_goal.description = _description + twitch_creator_goal.current_amount = _current_amount + twitch_creator_goal.target_amount = _target_amount + twitch_creator_goal.created_at = _created_at + return twitch_creator_goal + + +static func from_json(d: Dictionary) -> TwitchCreatorGoal: + var result: TwitchCreatorGoal = TwitchCreatorGoal.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("current_amount", null) != null: + result.current_amount = d["current_amount"] + if d.get("target_amount", null) != null: + result.target_amount = d["target_amount"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + return result diff --git a/addons/twitcher/generated/twitch_creator_goal.gd.uid b/addons/twitcher/generated/twitch_creator_goal.gd.uid new file mode 100644 index 00000000..2dc03bf7 --- /dev/null +++ b/addons/twitcher/generated/twitch_creator_goal.gd.uid @@ -0,0 +1 @@ +uid://d6ek35kfjk3e diff --git a/addons/twitcher/generated/twitch_custom_reward.gd b/addons/twitcher/generated/twitch_custom_reward.gd new file mode 100644 index 00000000..9cbedb0b --- /dev/null +++ b/addons/twitcher/generated/twitch_custom_reward.gd @@ -0,0 +1,399 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CustomReward +class_name TwitchCustomReward + +## The ID that uniquely identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The ID that uniquely identifies this custom reward. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The title of the reward. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The prompt shown to the viewer when they redeem the reward if user input is required. See the `is_user_input_required` field. +@export var prompt: String: + set(val): + prompt = val + track_data(&"prompt", val) + +## The cost of the reward in Channel Points. +@export var cost: int: + set(val): + cost = val + track_data(&"cost", val) + +## A set of custom images for the reward. This field is **null** if the broadcaster didn’t upload images. +@export var image: TwitchImage: + set(val): + image = val + track_data(&"image", val) + +## A set of default images for the reward. +@export var default_image: DefaultImage: + set(val): + default_image = val + track_data(&"default_image", val) + +## The background color to use for the reward. The color is in Hex format (for example, #00E5CB). +@export var background_color: String: + set(val): + background_color = val + track_data(&"background_color", val) + +## A Boolean value that determines whether the reward is enabled. Is **true** if enabled; otherwise, **false**. Disabled rewards aren’t shown to the user. +@export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + +## A Boolean value that determines whether the user must enter information when they redeem the reward. Is **true** if the user is prompted. +@export var is_user_input_required: bool: + set(val): + is_user_input_required = val + track_data(&"is_user_input_required", val) + +## The settings used to determine whether to apply a maximum to the number of redemptions allowed per live stream. +@export var max_per_stream_setting: MaxPerStreamSetting: + set(val): + max_per_stream_setting = val + track_data(&"max_per_stream_setting", val) + +## The settings used to determine whether to apply a maximum to the number of redemptions allowed per user per live stream. +@export var max_per_user_per_stream_setting: MaxPerUserPerStreamSetting: + set(val): + max_per_user_per_stream_setting = val + track_data(&"max_per_user_per_stream_setting", val) + +## The settings used to determine whether to apply a cooldown period between redemptions and the length of the cooldown. +@export var global_cooldown_setting: GlobalCooldownSetting: + set(val): + global_cooldown_setting = val + track_data(&"global_cooldown_setting", val) + +## A Boolean value that determines whether the reward is currently paused. Is **true** if the reward is paused. Viewers can’t redeem paused rewards. +@export var is_paused: bool: + set(val): + is_paused = val + track_data(&"is_paused", val) + +## A Boolean value that determines whether the reward is currently in stock. Is **true** if the reward is in stock. Viewers can’t redeem out of stock rewards. +@export var is_in_stock: bool: + set(val): + is_in_stock = val + track_data(&"is_in_stock", val) + +## A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed. If **false**, status is set to UNFULFILLED and follows the normal request queue process. +@export var should_redemptions_skip_request_queue: bool: + set(val): + should_redemptions_skip_request_queue = val + track_data(&"should_redemptions_skip_request_queue", val) + +## The number of redemptions redeemed during the current live stream. The number counts against the `max_per_stream_setting` limit. This field is **null** if the broadcaster’s stream isn’t live or _max\_per\_stream\_setting_ isn’t enabled. +@export var redemptions_redeemed_current_stream: int: + set(val): + redemptions_redeemed_current_stream = val + track_data(&"redemptions_redeemed_current_stream", val) + +## The timestamp of when the cooldown period expires. Is **null** if the reward isn’t in a cooldown state. See the `global_cooldown_setting` field. +@export var cooldown_expires_at: String: + set(val): + cooldown_expires_at = val + track_data(&"cooldown_expires_at", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _id: String, _title: String, _prompt: String, _cost: int, _image: TwitchImage, _default_image: DefaultImage, _background_color: String, _is_enabled: bool, _is_user_input_required: bool, _max_per_stream_setting: MaxPerStreamSetting, _max_per_user_per_stream_setting: MaxPerUserPerStreamSetting, _global_cooldown_setting: GlobalCooldownSetting, _is_paused: bool, _is_in_stock: bool, _should_redemptions_skip_request_queue: bool, _redemptions_redeemed_current_stream: int, _cooldown_expires_at: String) -> TwitchCustomReward: + var twitch_custom_reward: TwitchCustomReward = TwitchCustomReward.new() + twitch_custom_reward.broadcaster_id = _broadcaster_id + twitch_custom_reward.broadcaster_login = _broadcaster_login + twitch_custom_reward.broadcaster_name = _broadcaster_name + twitch_custom_reward.id = _id + twitch_custom_reward.title = _title + twitch_custom_reward.prompt = _prompt + twitch_custom_reward.cost = _cost + twitch_custom_reward.image = _image + twitch_custom_reward.default_image = _default_image + twitch_custom_reward.background_color = _background_color + twitch_custom_reward.is_enabled = _is_enabled + twitch_custom_reward.is_user_input_required = _is_user_input_required + twitch_custom_reward.max_per_stream_setting = _max_per_stream_setting + twitch_custom_reward.max_per_user_per_stream_setting = _max_per_user_per_stream_setting + twitch_custom_reward.global_cooldown_setting = _global_cooldown_setting + twitch_custom_reward.is_paused = _is_paused + twitch_custom_reward.is_in_stock = _is_in_stock + twitch_custom_reward.should_redemptions_skip_request_queue = _should_redemptions_skip_request_queue + twitch_custom_reward.redemptions_redeemed_current_stream = _redemptions_redeemed_current_stream + twitch_custom_reward.cooldown_expires_at = _cooldown_expires_at + return twitch_custom_reward + + +static func from_json(d: Dictionary) -> TwitchCustomReward: + var result: TwitchCustomReward = TwitchCustomReward.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("prompt", null) != null: + result.prompt = d["prompt"] + if d.get("cost", null) != null: + result.cost = d["cost"] + if d.get("image", null) != null: + result.image = TwitchImage.from_json(d["image"]) + if d.get("default_image", null) != null: + result.default_image = DefaultImage.from_json(d["default_image"]) + if d.get("background_color", null) != null: + result.background_color = d["background_color"] + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("is_user_input_required", null) != null: + result.is_user_input_required = d["is_user_input_required"] + if d.get("max_per_stream_setting", null) != null: + result.max_per_stream_setting = MaxPerStreamSetting.from_json(d["max_per_stream_setting"]) + if d.get("max_per_user_per_stream_setting", null) != null: + result.max_per_user_per_stream_setting = MaxPerUserPerStreamSetting.from_json(d["max_per_user_per_stream_setting"]) + if d.get("global_cooldown_setting", null) != null: + result.global_cooldown_setting = GlobalCooldownSetting.from_json(d["global_cooldown_setting"]) + if d.get("is_paused", null) != null: + result.is_paused = d["is_paused"] + if d.get("is_in_stock", null) != null: + result.is_in_stock = d["is_in_stock"] + if d.get("should_redemptions_skip_request_queue", null) != null: + result.should_redemptions_skip_request_queue = d["should_redemptions_skip_request_queue"] + if d.get("redemptions_redeemed_current_stream", null) != null: + result.redemptions_redeemed_current_stream = d["redemptions_redeemed_current_stream"] + if d.get("cooldown_expires_at", null) != null: + result.cooldown_expires_at = d["cooldown_expires_at"] + return result + + + +## A set of custom images for the reward. This field is **null** if the broadcaster didn’t upload images. +## #/components/schemas/CustomReward/Image +class TwitchImage extends TwitchData: + + ## The URL to a small version of the image. + @export var url_1x: String: + set(val): + url_1x = val + track_data(&"url_1x", val) + + ## The URL to a medium version of the image. + @export var url_2x: String: + set(val): + url_2x = val + track_data(&"url_2x", val) + + ## The URL to a large version of the image. + @export var url_4x: String: + set(val): + url_4x = val + track_data(&"url_4x", val) + + + + ## Constructor with all required fields. + static func create(_url_1x: String, _url_2x: String, _url_4x: String) -> TwitchImage: + var twitch_image: TwitchImage = TwitchImage.new() + twitch_image.url_1x = _url_1x + twitch_image.url_2x = _url_2x + twitch_image.url_4x = _url_4x + return twitch_image + + + static func from_json(d: Dictionary) -> TwitchImage: + var result: TwitchImage = TwitchImage.new() + if d.get("url_1x", null) != null: + result.url_1x = d["url_1x"] + if d.get("url_2x", null) != null: + result.url_2x = d["url_2x"] + if d.get("url_4x", null) != null: + result.url_4x = d["url_4x"] + return result + + + +## A set of default images for the reward. +## #/components/schemas/CustomReward/DefaultImage +class DefaultImage extends TwitchData: + + ## The URL to a small version of the image. + @export var url_1x: String: + set(val): + url_1x = val + track_data(&"url_1x", val) + + ## The URL to a medium version of the image. + @export var url_2x: String: + set(val): + url_2x = val + track_data(&"url_2x", val) + + ## The URL to a large version of the image. + @export var url_4x: String: + set(val): + url_4x = val + track_data(&"url_4x", val) + + + + ## Constructor with all required fields. + static func create(_url_1x: String, _url_2x: String, _url_4x: String) -> DefaultImage: + var default_image: DefaultImage = DefaultImage.new() + default_image.url_1x = _url_1x + default_image.url_2x = _url_2x + default_image.url_4x = _url_4x + return default_image + + + static func from_json(d: Dictionary) -> DefaultImage: + var result: DefaultImage = DefaultImage.new() + if d.get("url_1x", null) != null: + result.url_1x = d["url_1x"] + if d.get("url_2x", null) != null: + result.url_2x = d["url_2x"] + if d.get("url_4x", null) != null: + result.url_4x = d["url_4x"] + return result + + + +## The settings used to determine whether to apply a maximum to the number of redemptions allowed per live stream. +## #/components/schemas/CustomReward/MaxPerStreamSetting +class MaxPerStreamSetting extends TwitchData: + + ## A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per live stream. Is **true** if the reward applies a limit. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + ## The maximum number of redemptions allowed per live stream. + @export var max_per_stream: int: + set(val): + max_per_stream = val + track_data(&"max_per_stream", val) + + + + ## Constructor with all required fields. + static func create(_is_enabled: bool, _max_per_stream: int) -> MaxPerStreamSetting: + var max_per_stream_setting: MaxPerStreamSetting = MaxPerStreamSetting.new() + max_per_stream_setting.is_enabled = _is_enabled + max_per_stream_setting.max_per_stream = _max_per_stream + return max_per_stream_setting + + + static func from_json(d: Dictionary) -> MaxPerStreamSetting: + var result: MaxPerStreamSetting = MaxPerStreamSetting.new() + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("max_per_stream", null) != null: + result.max_per_stream = d["max_per_stream"] + return result + + + +## The settings used to determine whether to apply a maximum to the number of redemptions allowed per user per live stream. +## #/components/schemas/CustomReward/MaxPerUserPerStreamSetting +class MaxPerUserPerStreamSetting extends TwitchData: + + ## A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per user per live stream. Is **true** if the reward applies a limit. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + ## The maximum number of redemptions allowed per user per live stream. + @export var max_per_user_per_stream: int: + set(val): + max_per_user_per_stream = val + track_data(&"max_per_user_per_stream", val) + + + + ## Constructor with all required fields. + static func create(_is_enabled: bool, _max_per_user_per_stream: int) -> MaxPerUserPerStreamSetting: + var max_per_user_per_stream_setting: MaxPerUserPerStreamSetting = MaxPerUserPerStreamSetting.new() + max_per_user_per_stream_setting.is_enabled = _is_enabled + max_per_user_per_stream_setting.max_per_user_per_stream = _max_per_user_per_stream + return max_per_user_per_stream_setting + + + static func from_json(d: Dictionary) -> MaxPerUserPerStreamSetting: + var result: MaxPerUserPerStreamSetting = MaxPerUserPerStreamSetting.new() + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("max_per_user_per_stream", null) != null: + result.max_per_user_per_stream = d["max_per_user_per_stream"] + return result + + + +## The settings used to determine whether to apply a cooldown period between redemptions and the length of the cooldown. +## #/components/schemas/CustomReward/GlobalCooldownSetting +class GlobalCooldownSetting extends TwitchData: + + ## A Boolean value that determines whether to apply a cooldown period. Is **true** if a cooldown period is enabled. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + ## The cooldown period, in seconds. + @export var global_cooldown_seconds: int: + set(val): + global_cooldown_seconds = val + track_data(&"global_cooldown_seconds", val) + + + + ## Constructor with all required fields. + static func create(_is_enabled: bool, _global_cooldown_seconds: int) -> GlobalCooldownSetting: + var global_cooldown_setting: GlobalCooldownSetting = GlobalCooldownSetting.new() + global_cooldown_setting.is_enabled = _is_enabled + global_cooldown_setting.global_cooldown_seconds = _global_cooldown_seconds + return global_cooldown_setting + + + static func from_json(d: Dictionary) -> GlobalCooldownSetting: + var result: GlobalCooldownSetting = GlobalCooldownSetting.new() + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("global_cooldown_seconds", null) != null: + result.global_cooldown_seconds = d["global_cooldown_seconds"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_custom_reward.gd.uid b/addons/twitcher/generated/twitch_custom_reward.gd.uid new file mode 100644 index 00000000..c0c3f5af --- /dev/null +++ b/addons/twitcher/generated/twitch_custom_reward.gd.uid @@ -0,0 +1 @@ +uid://d34uaswkjcjbo diff --git a/addons/twitcher/generated/twitch_custom_reward_redemption.gd b/addons/twitcher/generated/twitch_custom_reward_redemption.gd new file mode 100644 index 00000000..2e28a993 --- /dev/null +++ b/addons/twitcher/generated/twitch_custom_reward_redemption.gd @@ -0,0 +1,178 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/CustomRewardRedemption +class_name TwitchCustomRewardRedemption + +## The ID that uniquely identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The ID that uniquely identifies this redemption.. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The ID of the user that redeemed the reward. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## An object that describes the reward that the user redeemed. +@export var reward: Reward: + set(val): + reward = val + track_data(&"reward", val) + +## The text that the user entered at the prompt when they redeemed the reward; otherwise, an empty string if user input was not required. +@export var user_input: String: + set(val): + user_input = val + track_data(&"user_input", val) + +## The state of the redemption. Possible values are: +## +## * CANCELED +## * FULFILLED +## * UNFULFILLED +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## The date and time of when the reward was redeemed, in RFC3339 format. +@export var redeemed_at: String: + set(val): + redeemed_at = val + track_data(&"redeemed_at", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _id: String, _user_id: String, _user_name: String, _user_login: String, _reward: Reward, _user_input: String, _status: String, _redeemed_at: String) -> TwitchCustomRewardRedemption: + var twitch_custom_reward_redemption: TwitchCustomRewardRedemption = TwitchCustomRewardRedemption.new() + twitch_custom_reward_redemption.broadcaster_id = _broadcaster_id + twitch_custom_reward_redemption.broadcaster_login = _broadcaster_login + twitch_custom_reward_redemption.broadcaster_name = _broadcaster_name + twitch_custom_reward_redemption.id = _id + twitch_custom_reward_redemption.user_id = _user_id + twitch_custom_reward_redemption.user_name = _user_name + twitch_custom_reward_redemption.user_login = _user_login + twitch_custom_reward_redemption.reward = _reward + twitch_custom_reward_redemption.user_input = _user_input + twitch_custom_reward_redemption.status = _status + twitch_custom_reward_redemption.redeemed_at = _redeemed_at + return twitch_custom_reward_redemption + + +static func from_json(d: Dictionary) -> TwitchCustomRewardRedemption: + var result: TwitchCustomRewardRedemption = TwitchCustomRewardRedemption.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("reward", null) != null: + result.reward = Reward.from_json(d["reward"]) + if d.get("user_input", null) != null: + result.user_input = d["user_input"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("redeemed_at", null) != null: + result.redeemed_at = d["redeemed_at"] + return result + + + +## An object that describes the reward that the user redeemed. +## #/components/schemas/CustomRewardRedemption/Reward +class Reward extends TwitchData: + + ## The ID that uniquely identifies the reward. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The reward’s title. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The prompt displayed to the viewer if user input is required. + @export var prompt: String: + set(val): + prompt = val + track_data(&"prompt", val) + + ## The reward’s cost, in Channel Points. + @export var cost: int: + set(val): + cost = val + track_data(&"cost", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _title: String, _prompt: String, _cost: int) -> Reward: + var reward: Reward = Reward.new() + reward.id = _id + reward.title = _title + reward.prompt = _prompt + reward.cost = _cost + return reward + + + static func from_json(d: Dictionary) -> Reward: + var result: Reward = Reward.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("prompt", null) != null: + result.prompt = d["prompt"] + if d.get("cost", null) != null: + result.cost = d["cost"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_custom_reward_redemption.gd.uid b/addons/twitcher/generated/twitch_custom_reward_redemption.gd.uid new file mode 100644 index 00000000..6e3a701a --- /dev/null +++ b/addons/twitcher/generated/twitch_custom_reward_redemption.gd.uid @@ -0,0 +1 @@ +uid://0s4apewkcan0 diff --git a/addons/twitcher/generated/twitch_delete_chat_messages.gd b/addons/twitcher/generated/twitch_delete_chat_messages.gd new file mode 100644 index 00000000..5e88ae83 --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_chat_messages.gd @@ -0,0 +1,39 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchDeleteChatMessages + + + +## All optional parameters for TwitchAPI.delete_chat_messages +## #/components/schemas/DeleteChatMessagesOpt +class Opt extends TwitchData: + + ## The ID of the message to remove. The `id` tag in the [PRIVMSG](https://dev.twitch.tv/docs/irc/tags#privmsg-tags) tag contains the message’s ID. Restrictions: + ## + ## * The message must have been created within the last 6 hours. + ## * The message must not belong to the broadcaster. + ## * The message must not belong to another moderator. + ## + ## If not specified, the request removes all messages in the broadcaster’s chat room. + @export var message_id: String: + set(val): + message_id = val + track_data(&"message_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("message_id", null) != null: + result.message_id = d["message_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_delete_chat_messages.gd.uid b/addons/twitcher/generated/twitch_delete_chat_messages.gd.uid new file mode 100644 index 00000000..fc529411 --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_chat_messages.gd.uid @@ -0,0 +1 @@ +uid://bifgk3c6tnogu diff --git a/addons/twitcher/generated/twitch_delete_guest_star_slot.gd b/addons/twitcher/generated/twitch_delete_guest_star_slot.gd new file mode 100644 index 00000000..92debee4 --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_guest_star_slot.gd @@ -0,0 +1,33 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchDeleteGuestStarSlot + + + +## All optional parameters for TwitchAPI.delete_guest_star_slot +## #/components/schemas/DeleteGuestStarSlotOpt +class Opt extends TwitchData: + + ## Flag signaling that the guest should be reinvited to the session, sending them back to the invite queue. + @export var should_reinvite_guest: String: + set(val): + should_reinvite_guest = val + track_data(&"should_reinvite_guest", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("should_reinvite_guest", null) != null: + result.should_reinvite_guest = d["should_reinvite_guest"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_delete_guest_star_slot.gd.uid b/addons/twitcher/generated/twitch_delete_guest_star_slot.gd.uid new file mode 100644 index 00000000..8a0d354d --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_guest_star_slot.gd.uid @@ -0,0 +1 @@ +uid://cumi77bjenewb diff --git a/addons/twitcher/generated/twitch_delete_videos.gd b/addons/twitcher/generated/twitch_delete_videos.gd new file mode 100644 index 00000000..b2de60e2 --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_videos.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchDeleteVideos + + + +## +## #/components/schemas/DeleteVideosResponse +class Response extends TwitchData: + + ## The list of IDs of the videos that were deleted. + @export var data: Array[String]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[String]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(value) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_delete_videos.gd.uid b/addons/twitcher/generated/twitch_delete_videos.gd.uid new file mode 100644 index 00000000..aef777a0 --- /dev/null +++ b/addons/twitcher/generated/twitch_delete_videos.gd.uid @@ -0,0 +1 @@ +uid://e4uvq6ctah05 diff --git a/addons/twitcher/generated/twitch_drops_entitlement.gd b/addons/twitcher/generated/twitch_drops_entitlement.gd new file mode 100644 index 00000000..545be223 --- /dev/null +++ b/addons/twitcher/generated/twitch_drops_entitlement.gd @@ -0,0 +1,86 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/DropsEntitlement +class_name TwitchDropsEntitlement + +## An ID that identifies the entitlement. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the benefit (reward). +@export var benefit_id: String: + set(val): + benefit_id = val + track_data(&"benefit_id", val) + +## The UTC date and time (in RFC3339 format) of when the entitlement was granted. +@export var timestamp: String: + set(val): + timestamp = val + track_data(&"timestamp", val) + +## An ID that identifies the user who was granted the entitlement. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## An ID that identifies the game the user was playing when the reward was entitled. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The entitlement’s fulfillment status. Possible values are: +## +## * CLAIMED +## * FULFILLED +@export var fulfillment_status: String: + set(val): + fulfillment_status = val + track_data(&"fulfillment_status", val) + +## The UTC date and time (in RFC3339 format) of when the entitlement was last updated. +@export var last_updated: String: + set(val): + last_updated = val + track_data(&"last_updated", val) + + + +## Constructor with all required fields. +static func create(_id: String, _benefit_id: String, _timestamp: String, _user_id: String, _game_id: String, _fulfillment_status: String, _last_updated: String) -> TwitchDropsEntitlement: + var twitch_drops_entitlement: TwitchDropsEntitlement = TwitchDropsEntitlement.new() + twitch_drops_entitlement.id = _id + twitch_drops_entitlement.benefit_id = _benefit_id + twitch_drops_entitlement.timestamp = _timestamp + twitch_drops_entitlement.user_id = _user_id + twitch_drops_entitlement.game_id = _game_id + twitch_drops_entitlement.fulfillment_status = _fulfillment_status + twitch_drops_entitlement.last_updated = _last_updated + return twitch_drops_entitlement + + +static func from_json(d: Dictionary) -> TwitchDropsEntitlement: + var result: TwitchDropsEntitlement = TwitchDropsEntitlement.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("benefit_id", null) != null: + result.benefit_id = d["benefit_id"] + if d.get("timestamp", null) != null: + result.timestamp = d["timestamp"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("fulfillment_status", null) != null: + result.fulfillment_status = d["fulfillment_status"] + if d.get("last_updated", null) != null: + result.last_updated = d["last_updated"] + return result diff --git a/addons/twitcher/generated/twitch_drops_entitlement.gd.uid b/addons/twitcher/generated/twitch_drops_entitlement.gd.uid new file mode 100644 index 00000000..a9d36bd3 --- /dev/null +++ b/addons/twitcher/generated/twitch_drops_entitlement.gd.uid @@ -0,0 +1 @@ +uid://dd4h7ue40wrt0 diff --git a/addons/twitcher/generated/twitch_drops_entitlement_updated.gd b/addons/twitcher/generated/twitch_drops_entitlement_updated.gd new file mode 100644 index 00000000..ed7837d5 --- /dev/null +++ b/addons/twitcher/generated/twitch_drops_entitlement_updated.gd @@ -0,0 +1,45 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/DropsEntitlementUpdated +class_name TwitchDropsEntitlementUpdated + +## A string that indicates whether the status of the entitlements in the `ids` field were successfully updated. Possible values are: +## +## * INVALID\_ID — The entitlement IDs in the `ids` field are not valid. +## * NOT\_FOUND — The entitlement IDs in the `ids` field were not found. +## * SUCCESS — The status of the entitlements in the `ids` field were successfully updated. +## * UNAUTHORIZED — The user or organization identified by the user access token is not authorized to update the entitlements. +## * UPDATE\_FAILED — The update failed. These are considered transient errors and the request should be retried later. +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## The list of entitlements that the status in the `status` field applies to. +@export var ids: Array[String]: + set(val): + ids = val + track_data(&"ids", val) + + + +## Constructor with all required fields. +static func create(_status: String, _ids: Array[String]) -> TwitchDropsEntitlementUpdated: + var twitch_drops_entitlement_updated: TwitchDropsEntitlementUpdated = TwitchDropsEntitlementUpdated.new() + twitch_drops_entitlement_updated.status = _status + twitch_drops_entitlement_updated.ids = _ids + return twitch_drops_entitlement_updated + + +static func from_json(d: Dictionary) -> TwitchDropsEntitlementUpdated: + var result: TwitchDropsEntitlementUpdated = TwitchDropsEntitlementUpdated.new() + if d.get("status", null) != null: + result.status = d["status"] + if d.get("ids", null) != null: + for value in d["ids"]: + result.ids.append(value) + return result diff --git a/addons/twitcher/generated/twitch_drops_entitlement_updated.gd.uid b/addons/twitcher/generated/twitch_drops_entitlement_updated.gd.uid new file mode 100644 index 00000000..fffd80d6 --- /dev/null +++ b/addons/twitcher/generated/twitch_drops_entitlement_updated.gd.uid @@ -0,0 +1 @@ +uid://b8bl0d0wy1n0f diff --git a/addons/twitcher/generated/twitch_emote.gd b/addons/twitcher/generated/twitch_emote.gd new file mode 100644 index 00000000..2667ebc9 --- /dev/null +++ b/addons/twitcher/generated/twitch_emote.gd @@ -0,0 +1,168 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Emote +class_name TwitchEmote + +## An ID that uniquely identifies this emote. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +@export var images: Images: + set(val): + images = val + track_data(&"images", val) + +## The type of emote. The possible values are: +## +## * bitstier — A Bits tier emote. +## * follower — A follower emote. +## * subscriptions — A subscriber emote. +@export var emote_type: String: + set(val): + emote_type = val + track_data(&"emote_type", val) + +## An ID that identifies the emote set that the emote belongs to. +@export var emote_set_id: String: + set(val): + emote_set_id = val + track_data(&"emote_set_id", val) + +## The ID of the broadcaster who owns the emote. +@export var owner_id: String: + set(val): + owner_id = val + track_data(&"owner_id", val) + +## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only `static`. But if the emote is available as a static PNG and an animated GIF, the array contains `static` and `animated`. The possible formats are: +## +## * animated — An animated GIF is available for this emote. +## * static — A static PNG file is available for this emote. +@export var format: Array[String]: + set(val): + format = val + track_data(&"format", val) + +## The sizes that the emote is available in. For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0\. Possible sizes are: +## +## * 1.0 — A small version (28px x 28px) is available. +## * 2.0 — A medium version (56px x 56px) is available. +## * 3.0 — A large version (112px x 112px) is available. +@export var scale: Array[String]: + set(val): + scale = val + track_data(&"scale", val) + +## The background themes that the emote is available in. Possible themes are: +## +## * dark +## * light +@export var theme_mode: Array[String]: + set(val): + theme_mode = val + track_data(&"theme_mode", val) + + + +## Constructor with all required fields. +static func create(_id: String, _name: String, _images: Images, _emote_type: String, _emote_set_id: String, _owner_id: String, _format: Array[String], _scale: Array[String], _theme_mode: Array[String]) -> TwitchEmote: + var twitch_emote: TwitchEmote = TwitchEmote.new() + twitch_emote.id = _id + twitch_emote.name = _name + twitch_emote.images = _images + twitch_emote.emote_type = _emote_type + twitch_emote.emote_set_id = _emote_set_id + twitch_emote.owner_id = _owner_id + twitch_emote.format = _format + twitch_emote.scale = _scale + twitch_emote.theme_mode = _theme_mode + return twitch_emote + + +static func from_json(d: Dictionary) -> TwitchEmote: + var result: TwitchEmote = TwitchEmote.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("images", null) != null: + result.images = Images.from_json(d["images"]) + if d.get("emote_type", null) != null: + result.emote_type = d["emote_type"] + if d.get("emote_set_id", null) != null: + result.emote_set_id = d["emote_set_id"] + if d.get("owner_id", null) != null: + result.owner_id = d["owner_id"] + if d.get("format", null) != null: + for value in d["format"]: + result.format.append(value) + if d.get("scale", null) != null: + for value in d["scale"]: + result.scale.append(value) + if d.get("theme_mode", null) != null: + for value in d["theme_mode"]: + result.theme_mode.append(value) + return result + + + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +## #/components/schemas/Emote/Images +class Images extends TwitchData: + + ## A URL to the small version (28px x 28px) of the emote. + @export var url_1x: String: + set(val): + url_1x = val + track_data(&"url_1x", val) + + ## A URL to the medium version (56px x 56px) of the emote. + @export var url_2x: String: + set(val): + url_2x = val + track_data(&"url_2x", val) + + ## A URL to the large version (112px x 112px) of the emote. + @export var url_4x: String: + set(val): + url_4x = val + track_data(&"url_4x", val) + + + + ## Constructor with all required fields. + static func create(_url_1x: String, _url_2x: String, _url_4x: String) -> Images: + var images: Images = Images.new() + images.url_1x = _url_1x + images.url_2x = _url_2x + images.url_4x = _url_4x + return images + + + static func from_json(d: Dictionary) -> Images: + var result: Images = Images.new() + if d.get("url_1x", null) != null: + result.url_1x = d["url_1x"] + if d.get("url_2x", null) != null: + result.url_2x = d["url_2x"] + if d.get("url_4x", null) != null: + result.url_4x = d["url_4x"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_emote.gd.uid b/addons/twitcher/generated/twitch_emote.gd.uid new file mode 100644 index 00000000..6eb7256d --- /dev/null +++ b/addons/twitcher/generated/twitch_emote.gd.uid @@ -0,0 +1 @@ +uid://cr8cnwu0wlel6 diff --git a/addons/twitcher/generated/twitch_end_guest_star_session.gd b/addons/twitcher/generated/twitch_end_guest_star_session.gd new file mode 100644 index 00000000..7ac5b978 --- /dev/null +++ b/addons/twitcher/generated/twitch_end_guest_star_session.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchEndGuestStarSession + + + +## +## #/components/schemas/EndGuestStarSessionResponse +class Response extends TwitchData: + + ## Summary of the session details when the session was ended. + @export var data: Array[TwitchGuestStarSession]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGuestStarSession]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGuestStarSession.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_end_guest_star_session.gd.uid b/addons/twitcher/generated/twitch_end_guest_star_session.gd.uid new file mode 100644 index 00000000..07146ce1 --- /dev/null +++ b/addons/twitcher/generated/twitch_end_guest_star_session.gd.uid @@ -0,0 +1 @@ +uid://dnv76mguitjfe diff --git a/addons/twitcher/generated/twitch_end_poll.gd b/addons/twitcher/generated/twitch_end_poll.gd new file mode 100644 index 00000000..81ba0068 --- /dev/null +++ b/addons/twitcher/generated/twitch_end_poll.gd @@ -0,0 +1,83 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchEndPoll + + + +## +## #/components/schemas/EndPollBody +class Body extends TwitchData: + + ## The ID of the broadcaster that’s running the poll. This ID must match the user ID in the user access token. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The ID of the poll to update. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The status to set the poll to. Possible case-sensitive values are: + ## + ## * TERMINATED — Ends the poll before the poll is scheduled to end. The poll remains publicly visible. + ## * ARCHIVED — Ends the poll before the poll is scheduled to end, and then archives it so it's no longer publicly visible. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _id: String, _status: String) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.id = _id + body.status = _status + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("status", null) != null: + result.status = d["status"] + return result + + + +## +## #/components/schemas/EndPollResponse +class Response extends TwitchData: + + ## A list that contains the poll that you ended. + @export var data: Array[TwitchPoll]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPoll]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPoll.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_end_poll.gd.uid b/addons/twitcher/generated/twitch_end_poll.gd.uid new file mode 100644 index 00000000..4cb6156e --- /dev/null +++ b/addons/twitcher/generated/twitch_end_poll.gd.uid @@ -0,0 +1 @@ +uid://c85hwqlquv0me diff --git a/addons/twitcher/generated/twitch_end_prediction.gd b/addons/twitcher/generated/twitch_end_prediction.gd new file mode 100644 index 00000000..d6f81027 --- /dev/null +++ b/addons/twitcher/generated/twitch_end_prediction.gd @@ -0,0 +1,96 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchEndPrediction + + + +## +## #/components/schemas/EndPredictionBody +class Body extends TwitchData: + + ## The ID of the broadcaster that’s running the prediction. This ID must match the user ID in the user access token. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The ID of the prediction to update. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The status to set the prediction to. Possible case-sensitive values are: + ## + ## * RESOLVED — The winning outcome is determined and the Channel Points are distributed to the viewers who predicted the correct outcome. + ## * CANCELED — The broadcaster is canceling the prediction and sending refunds to the participants. + ## * LOCKED — The broadcaster is locking the prediction, which means viewers may no longer make predictions. + ## + ## The broadcaster can update an active prediction to LOCKED, RESOLVED, or CANCELED; and update a locked prediction to RESOLVED or CANCELED. + ## + ## The broadcaster has up to 24 hours after the prediction window closes to resolve the prediction. If not, Twitch sets the status to CANCELED and returns the points. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## The ID of the winning outcome. You must set this parameter if you set `status` to RESOLVED. + @export var winning_outcome_id: String: + set(val): + winning_outcome_id = val + track_data(&"winning_outcome_id", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _id: String, _status: String) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.id = _id + body.status = _status + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("winning_outcome_id", null) != null: + result.winning_outcome_id = d["winning_outcome_id"] + return result + + + +## +## #/components/schemas/EndPredictionResponse +class Response extends TwitchData: + + ## A list that contains the single prediction that you updated. + @export var data: Array[TwitchPrediction]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPrediction]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPrediction.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_end_prediction.gd.uid b/addons/twitcher/generated/twitch_end_prediction.gd.uid new file mode 100644 index 00000000..6472dca0 --- /dev/null +++ b/addons/twitcher/generated/twitch_end_prediction.gd.uid @@ -0,0 +1 @@ +uid://dlqtp0nl2h2ow diff --git a/addons/twitcher/generated/twitch_event_sub_subscription.gd b/addons/twitcher/generated/twitch_event_sub_subscription.gd new file mode 100644 index 00000000..22b22a9d --- /dev/null +++ b/addons/twitcher/generated/twitch_event_sub_subscription.gd @@ -0,0 +1,172 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/EventSubSubscription +class_name TwitchEventSubSubscription + +## An ID that identifies the subscription. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The subscription's status. The subscriber receives events only for **enabled** subscriptions. Possible values are: +## +## * enabled — The subscription is enabled. +## * webhook\_callback\_verification\_pending — The subscription is pending verification of the specified callback URL. +## * webhook\_callback\_verification\_failed — The specified callback URL failed verification. +## * notification\_failures\_exceeded — The notification delivery failure rate was too high. +## * authorization\_revoked — The authorization was revoked for one or more users specified in the **Condition** object. +## * moderator\_removed — The moderator that authorized the subscription is no longer one of the broadcaster's moderators. +## * user\_removed — One of the users specified in the **Condition** object was removed. +## * version\_removed — The subscription to subscription type and version is no longer supported. +## * beta\_maintenance — The subscription to the beta subscription type was removed due to maintenance. +## * websocket\_disconnected — The client closed the connection. +## * websocket\_failed\_ping\_pong — The client failed to respond to a ping message. +## * websocket\_received\_inbound\_traffic — The client sent a non-pong message. Clients may only send pong messages (and only in response to a ping message). +## * websocket\_connection\_unused — The client failed to subscribe to events within the required time. +## * websocket\_internal\_error — The Twitch WebSocket server experienced an unexpected error. +## * websocket\_network\_timeout — The Twitch WebSocket server timed out writing the message to the client. +## * websocket\_network\_error — The Twitch WebSocket server experienced a network error writing the message to the client. +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## The subscription's type. See [Subscription Types](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#subscription-types). +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The version number that identifies this definition of the subscription's data. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The subscription's parameter values. This is a string-encoded JSON object whose contents are determined by the subscription type. +@export var condition: Dictionary: + set(val): + condition = val + track_data(&"condition", val) + +## The date and time (in RFC3339 format) of when the subscription was created. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The transport details used to send the notifications. +@export var transport: Transport: + set(val): + transport = val + track_data(&"transport", val) + +## The amount that the subscription counts against your limit. [Learn More](https://dev.twitch.tv/docs/eventsub/manage-subscriptions/#subscription-limits) +@export var cost: int: + set(val): + cost = val + track_data(&"cost", val) + + + +## Constructor with all required fields. +static func create(_id: String, _status: String, _type: String, _version: String, _condition: Dictionary, _created_at: String, _transport: Transport, _cost: int) -> TwitchEventSubSubscription: + var twitch_event_sub_subscription: TwitchEventSubSubscription = TwitchEventSubSubscription.new() + twitch_event_sub_subscription.id = _id + twitch_event_sub_subscription.status = _status + twitch_event_sub_subscription.type = _type + twitch_event_sub_subscription.version = _version + twitch_event_sub_subscription.condition = _condition + twitch_event_sub_subscription.created_at = _created_at + twitch_event_sub_subscription.transport = _transport + twitch_event_sub_subscription.cost = _cost + return twitch_event_sub_subscription + + +static func from_json(d: Dictionary) -> TwitchEventSubSubscription: + var result: TwitchEventSubSubscription = TwitchEventSubSubscription.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("condition", null) != null: + result.condition = d["condition"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("transport", null) != null: + result.transport = Transport.from_json(d["transport"]) + if d.get("cost", null) != null: + result.cost = d["cost"] + return result + + + +## The transport details used to send the notifications. +## #/components/schemas/EventSubSubscription/Transport +class Transport extends TwitchData: + + ## The transport method. Possible values are: + ## + ## * webhook + ## * websocket + @export var method: String: + set(val): + method = val + track_data(&"method", val) + + ## The callback URL where the notifications are sent. Included only if `method` is set to **webhook**. + @export var callback: String: + set(val): + callback = val + track_data(&"callback", val) + + ## An ID that identifies the WebSocket that notifications are sent to. Included only if `method` is set to **websocket**. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + ## The UTC date and time that the WebSocket connection was established. Included only if `method` is set to **websocket**. + @export var connected_at: String: + set(val): + connected_at = val + track_data(&"connected_at", val) + + ## The UTC date and time that the WebSocket connection was lost. Included only if `method` is set to **websocket**. + @export var disconnected_at: String: + set(val): + disconnected_at = val + track_data(&"disconnected_at", val) + + + + ## Constructor with all required fields. + static func create(_method: String) -> Transport: + var transport: Transport = Transport.new() + transport.method = _method + return transport + + + static func from_json(d: Dictionary) -> Transport: + var result: Transport = Transport.new() + if d.get("method", null) != null: + result.method = d["method"] + if d.get("callback", null) != null: + result.callback = d["callback"] + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + if d.get("connected_at", null) != null: + result.connected_at = d["connected_at"] + if d.get("disconnected_at", null) != null: + result.disconnected_at = d["disconnected_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_event_sub_subscription.gd.uid b/addons/twitcher/generated/twitch_event_sub_subscription.gd.uid new file mode 100644 index 00000000..16c476d8 --- /dev/null +++ b/addons/twitcher/generated/twitch_event_sub_subscription.gd.uid @@ -0,0 +1 @@ +uid://e8nf7nbqecah diff --git a/addons/twitcher/generated/twitch_extension.gd b/addons/twitcher/generated/twitch_extension.gd new file mode 100644 index 00000000..09489739 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension.gd @@ -0,0 +1,538 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Extension +class_name TwitchExtension + +## The name of the user or organization that owns the extension. +@export var author_name: String: + set(val): + author_name = val + track_data(&"author_name", val) + +## A Boolean value that determines whether the extension has features that use Bits. Is **true** if the extension has features that use Bits. +@export var bits_enabled: bool: + set(val): + bits_enabled = val + track_data(&"bits_enabled", val) + +## A Boolean value that determines whether a user can install the extension on their channel. Is **true** if a user can install the extension. +## +## Typically, this is set to **false** if the extension is currently in testing mode and requires users to be allowlisted (the allowlist is configured on Twitch’s [developer site](https://dev.twitch.tv/console/extensions) under the **Extensions** \-> **Extension** \-> **Version** \-> **Access**). +@export var can_install: bool: + set(val): + can_install = val + track_data(&"can_install", val) + +## The location of where the extension’s configuration is stored. Possible values are: +## +## * hosted — The Extensions Configuration Service hosts the configuration. +## * custom — The Extension Backend Service (EBS) hosts the configuration. +## * none — The extension doesn't require configuration. +@export var configuration_location: String: + set(val): + configuration_location = val + track_data(&"configuration_location", val) + +## A longer description of the extension. It appears on the details page. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + +## A URL to the extension’s Terms of Service. +@export var eula_tos_url: String: + set(val): + eula_tos_url = val + track_data(&"eula_tos_url", val) + +## A Boolean value that determines whether the extension can communicate with the installed channel’s chat. Is **true** if the extension can communicate with the channel’s chat room. +@export var has_chat_support: bool: + set(val): + has_chat_support = val + track_data(&"has_chat_support", val) + +## A URL to the default icon that’s displayed in the Extensions directory. +@export var icon_url: String: + set(val): + icon_url = val + track_data(&"icon_url", val) + +## +@export var icon_urls: TwitchExtensionIconUrls: + set(val): + icon_urls = val + track_data(&"icon_urls", val) + +## The extension’s ID. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## A URL to the extension’s privacy policy. +@export var privacy_policy_url: String: + set(val): + privacy_policy_url = val + track_data(&"privacy_policy_url", val) + +## A Boolean value that determines whether the extension wants to explicitly ask viewers to link their Twitch identity. +@export var request_identity_link: bool: + set(val): + request_identity_link = val + track_data(&"request_identity_link", val) + +## A list of URLs to screenshots that are shown in the Extensions marketplace. +@export var screenshot_urls: Array[String]: + set(val): + screenshot_urls = val + track_data(&"screenshot_urls", val) + +## The extension’s state. Possible values are: +## +## * Approved +## * AssetsUploaded +## * Deleted +## * Deprecated +## * InReview +## * InTest +## * PendingAction +## * Rejected +## * Released +@export var state: String: + set(val): + state = val + track_data(&"state", val) + +## Indicates whether the extension can view the user’s subscription level on the channel that the extension is installed on. Possible values are: +## +## * none — The extension can't view the user’s subscription level. +## * optional — The extension can view the user’s subscription level. +@export var subscriptions_support_level: String: + set(val): + subscriptions_support_level = val + track_data(&"subscriptions_support_level", val) + +## A short description of the extension that streamers see when hovering over the discovery splash screen in the Extensions manager. +@export var summary: String: + set(val): + summary = val + track_data(&"summary", val) + +## The email address that users use to get support for the extension. +@export var support_email: String: + set(val): + support_email = val + track_data(&"support_email", val) + +## The extension’s version number. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## A brief description displayed on the channel to explain how the extension works. +@export var viewer_summary: String: + set(val): + viewer_summary = val + track_data(&"viewer_summary", val) + +## Describes all views-related information such as how the extension is displayed on mobile devices. +@export var views: Views: + set(val): + views = val + track_data(&"views", val) + +## Allowlisted configuration URLs for displaying the extension (the allowlist is configured on Twitch’s [developer site](https://dev.twitch.tv/console/extensions) under the **Extensions** \-> **Extension** \-> **Version** \-> **Capabilities**). +@export var allowlisted_config_urls: Array[String]: + set(val): + allowlisted_config_urls = val + track_data(&"allowlisted_config_urls", val) + +## Allowlisted panel URLs for displaying the extension (the allowlist is configured on Twitch’s [developer site](https://dev.twitch.tv/console/extensions) under the **Extensions** \-> **Extension** \-> **Version** \-> **Capabilities**). +@export var allowlisted_panel_urls: Array[String]: + set(val): + allowlisted_panel_urls = val + track_data(&"allowlisted_panel_urls", val) + + + +## Constructor with all required fields. +static func create(_author_name: String, _bits_enabled: bool, _can_install: bool, _configuration_location: String, _description: String, _eula_tos_url: String, _has_chat_support: bool, _icon_url: String, _icon_urls: TwitchExtensionIconUrls, _id: String, _name: String, _privacy_policy_url: String, _request_identity_link: bool, _screenshot_urls: Array[String], _state: String, _subscriptions_support_level: String, _summary: String, _support_email: String, _version: String, _viewer_summary: String, _views: Views, _allowlisted_config_urls: Array[String], _allowlisted_panel_urls: Array[String]) -> TwitchExtension: + var twitch_extension: TwitchExtension = TwitchExtension.new() + twitch_extension.author_name = _author_name + twitch_extension.bits_enabled = _bits_enabled + twitch_extension.can_install = _can_install + twitch_extension.configuration_location = _configuration_location + twitch_extension.description = _description + twitch_extension.eula_tos_url = _eula_tos_url + twitch_extension.has_chat_support = _has_chat_support + twitch_extension.icon_url = _icon_url + twitch_extension.icon_urls = _icon_urls + twitch_extension.id = _id + twitch_extension.name = _name + twitch_extension.privacy_policy_url = _privacy_policy_url + twitch_extension.request_identity_link = _request_identity_link + twitch_extension.screenshot_urls = _screenshot_urls + twitch_extension.state = _state + twitch_extension.subscriptions_support_level = _subscriptions_support_level + twitch_extension.summary = _summary + twitch_extension.support_email = _support_email + twitch_extension.version = _version + twitch_extension.viewer_summary = _viewer_summary + twitch_extension.views = _views + twitch_extension.allowlisted_config_urls = _allowlisted_config_urls + twitch_extension.allowlisted_panel_urls = _allowlisted_panel_urls + return twitch_extension + + +static func from_json(d: Dictionary) -> TwitchExtension: + var result: TwitchExtension = TwitchExtension.new() + if d.get("author_name", null) != null: + result.author_name = d["author_name"] + if d.get("bits_enabled", null) != null: + result.bits_enabled = d["bits_enabled"] + if d.get("can_install", null) != null: + result.can_install = d["can_install"] + if d.get("configuration_location", null) != null: + result.configuration_location = d["configuration_location"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("eula_tos_url", null) != null: + result.eula_tos_url = d["eula_tos_url"] + if d.get("has_chat_support", null) != null: + result.has_chat_support = d["has_chat_support"] + if d.get("icon_url", null) != null: + result.icon_url = d["icon_url"] + if d.get("icon_urls", null) != null: + result.icon_urls = TwitchExtensionIconUrls.from_json(d["icon_urls"]) + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("privacy_policy_url", null) != null: + result.privacy_policy_url = d["privacy_policy_url"] + if d.get("request_identity_link", null) != null: + result.request_identity_link = d["request_identity_link"] + if d.get("screenshot_urls", null) != null: + for value in d["screenshot_urls"]: + result.screenshot_urls.append(value) + if d.get("state", null) != null: + result.state = d["state"] + if d.get("subscriptions_support_level", null) != null: + result.subscriptions_support_level = d["subscriptions_support_level"] + if d.get("summary", null) != null: + result.summary = d["summary"] + if d.get("support_email", null) != null: + result.support_email = d["support_email"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("viewer_summary", null) != null: + result.viewer_summary = d["viewer_summary"] + if d.get("views", null) != null: + result.views = Views.from_json(d["views"]) + if d.get("allowlisted_config_urls", null) != null: + for value in d["allowlisted_config_urls"]: + result.allowlisted_config_urls.append(value) + if d.get("allowlisted_panel_urls", null) != null: + for value in d["allowlisted_panel_urls"]: + result.allowlisted_panel_urls.append(value) + return result + + + +## Describes all views-related information such as how the extension is displayed on mobile devices. +## #/components/schemas/Extension/Views +class Views extends TwitchData: + + ## Describes how the extension is displayed on mobile devices. + @export var mobile: Mobile: + set(val): + mobile = val + track_data(&"mobile", val) + + ## Describes how the extension is rendered if the extension may be activated as a panel extension. + @export var panel: TwitchPanel: + set(val): + panel = val + track_data(&"panel", val) + + ## Describes how the extension is rendered if the extension may be activated as a video-overlay extension. + @export var video_overlay: VideoOverlay: + set(val): + video_overlay = val + track_data(&"video_overlay", val) + + ## Describes how the extension is rendered if the extension may be activated as a video-component extension. + @export var component: Component: + set(val): + component = val + track_data(&"component", val) + + ## Describes the view that is shown to broadcasters while they are configuring your extension within the Extension Manager. + @export var config: Config: + set(val): + config = val + track_data(&"config", val) + + + + ## Constructor with all required fields. + static func create(_mobile: Mobile, _panel: TwitchPanel, _video_overlay: VideoOverlay, _component: Component, _config: Config) -> Views: + var views: Views = Views.new() + views.mobile = _mobile + views.panel = _panel + views.video_overlay = _video_overlay + views.component = _component + views.config = _config + return views + + + static func from_json(d: Dictionary) -> Views: + var result: Views = Views.new() + if d.get("mobile", null) != null: + result.mobile = Mobile.from_json(d["mobile"]) + if d.get("panel", null) != null: + result.panel = TwitchPanel.from_json(d["panel"]) + if d.get("video_overlay", null) != null: + result.video_overlay = VideoOverlay.from_json(d["video_overlay"]) + if d.get("component", null) != null: + result.component = Component.from_json(d["component"]) + if d.get("config", null) != null: + result.config = Config.from_json(d["config"]) + return result + + + +## Describes how the extension is displayed on mobile devices. +## #/components/schemas/Extension/Views/Mobile +class Mobile extends TwitchData: + + ## The HTML file that is shown to viewers on mobile devices. This page is presented to viewers as a panel behind the chat area of the mobile app. + @export var viewer_url: String: + set(val): + viewer_url = val + track_data(&"viewer_url", val) + + + + ## Constructor with all required fields. + static func create(_viewer_url: String) -> Mobile: + var mobile: Mobile = Mobile.new() + mobile.viewer_url = _viewer_url + return mobile + + + static func from_json(d: Dictionary) -> Mobile: + var result: Mobile = Mobile.new() + if d.get("viewer_url", null) != null: + result.viewer_url = d["viewer_url"] + return result + + + +## Describes how the extension is rendered if the extension may be activated as a panel extension. +## #/components/schemas/Extension/Views/Panel +class TwitchPanel extends TwitchData: + + ## The HTML file that is shown to viewers on the channel page when the extension is activated in a Panel slot. + @export var viewer_url: String: + set(val): + viewer_url = val + track_data(&"viewer_url", val) + + ## The height, in pixels, of the panel component that the extension is rendered in. + @export var height: int: + set(val): + height = val + track_data(&"height", val) + + ## A Boolean value that determines whether the extension can link to non-Twitch domains. + @export var can_link_external_content: bool: + set(val): + can_link_external_content = val + track_data(&"can_link_external_content", val) + + + + ## Constructor with all required fields. + static func create(_viewer_url: String, _height: int, _can_link_external_content: bool) -> TwitchPanel: + var twitch_panel: TwitchPanel = TwitchPanel.new() + twitch_panel.viewer_url = _viewer_url + twitch_panel.height = _height + twitch_panel.can_link_external_content = _can_link_external_content + return twitch_panel + + + static func from_json(d: Dictionary) -> TwitchPanel: + var result: TwitchPanel = TwitchPanel.new() + if d.get("viewer_url", null) != null: + result.viewer_url = d["viewer_url"] + if d.get("height", null) != null: + result.height = d["height"] + if d.get("can_link_external_content", null) != null: + result.can_link_external_content = d["can_link_external_content"] + return result + + + +## Describes how the extension is rendered if the extension may be activated as a video-overlay extension. +## #/components/schemas/Extension/Views/VideoOverlay +class VideoOverlay extends TwitchData: + + ## The HTML file that is shown to viewers on the channel page when the extension is activated on the Video - Overlay slot. + @export var viewer_url: String: + set(val): + viewer_url = val + track_data(&"viewer_url", val) + + ## A Boolean value that determines whether the extension can link to non-Twitch domains. + @export var can_link_external_content: bool: + set(val): + can_link_external_content = val + track_data(&"can_link_external_content", val) + + + + ## Constructor with all required fields. + static func create(_viewer_url: String, _can_link_external_content: bool) -> VideoOverlay: + var video_overlay: VideoOverlay = VideoOverlay.new() + video_overlay.viewer_url = _viewer_url + video_overlay.can_link_external_content = _can_link_external_content + return video_overlay + + + static func from_json(d: Dictionary) -> VideoOverlay: + var result: VideoOverlay = VideoOverlay.new() + if d.get("viewer_url", null) != null: + result.viewer_url = d["viewer_url"] + if d.get("can_link_external_content", null) != null: + result.can_link_external_content = d["can_link_external_content"] + return result + + + +## Describes how the extension is rendered if the extension may be activated as a video-component extension. +## #/components/schemas/Extension/Views/Component +class Component extends TwitchData: + + ## The HTML file that is shown to viewers on the channel page when the extension is activated in a Video - Component slot. + @export var viewer_url: String: + set(val): + viewer_url = val + track_data(&"viewer_url", val) + + ## The width value of the ratio (width : height) which determines the extension’s width, and how the extension’s iframe will resize in different video player environments. + @export var aspect_ratio_x: int: + set(val): + aspect_ratio_x = val + track_data(&"aspect_ratio_x", val) + + ## The height value of the ratio (width : height) which determines the extension’s height, and how the extension’s iframe will resize in different video player environments. + @export var aspect_ratio_y: int: + set(val): + aspect_ratio_y = val + track_data(&"aspect_ratio_y", val) + + ## A Boolean value that determines whether to apply CSS zoom. If **true**, a CSS zoom is applied such that the size of the extension is variable but the inner dimensions are fixed based on Scale Pixels. This allows your extension to render as if it is of fixed width and height. If **false**, the inner dimensions of the extension iframe are variable, meaning your extension must implement responsiveness. + @export var autoscale: bool: + set(val): + autoscale = val + track_data(&"autoscale", val) + + ## The base width, in pixels, of the extension to use when scaling (see `autoscale`). This value is ignored if `autoscale` is **false**. + @export var scale_pixels: int: + set(val): + scale_pixels = val + track_data(&"scale_pixels", val) + + ## The height as a percent of the maximum height of a video component extension. Values are between 1% - 100%. + @export var target_height: int: + set(val): + target_height = val + track_data(&"target_height", val) + + ## A Boolean value that determines whether the extension can link to non-Twitch domains. + @export var can_link_external_content: bool: + set(val): + can_link_external_content = val + track_data(&"can_link_external_content", val) + + + + ## Constructor with all required fields. + static func create(_viewer_url: String, _aspect_ratio_x: int, _aspect_ratio_y: int, _autoscale: bool, _scale_pixels: int, _target_height: int, _can_link_external_content: bool) -> Component: + var component: Component = Component.new() + component.viewer_url = _viewer_url + component.aspect_ratio_x = _aspect_ratio_x + component.aspect_ratio_y = _aspect_ratio_y + component.autoscale = _autoscale + component.scale_pixels = _scale_pixels + component.target_height = _target_height + component.can_link_external_content = _can_link_external_content + return component + + + static func from_json(d: Dictionary) -> Component: + var result: Component = Component.new() + if d.get("viewer_url", null) != null: + result.viewer_url = d["viewer_url"] + if d.get("aspect_ratio_x", null) != null: + result.aspect_ratio_x = d["aspect_ratio_x"] + if d.get("aspect_ratio_y", null) != null: + result.aspect_ratio_y = d["aspect_ratio_y"] + if d.get("autoscale", null) != null: + result.autoscale = d["autoscale"] + if d.get("scale_pixels", null) != null: + result.scale_pixels = d["scale_pixels"] + if d.get("target_height", null) != null: + result.target_height = d["target_height"] + if d.get("can_link_external_content", null) != null: + result.can_link_external_content = d["can_link_external_content"] + return result + + + +## Describes the view that is shown to broadcasters while they are configuring your extension within the Extension Manager. +## #/components/schemas/Extension/Views/Config +class Config extends TwitchData: + + ## The HTML file shown to broadcasters while they are configuring your extension within the Extension Manager. + @export var viewer_url: String: + set(val): + viewer_url = val + track_data(&"viewer_url", val) + + ## A Boolean value that determines whether the extension can link to non-Twitch domains. + @export var can_link_external_content: bool: + set(val): + can_link_external_content = val + track_data(&"can_link_external_content", val) + + + + ## Constructor with all required fields. + static func create(_viewer_url: String, _can_link_external_content: bool) -> Config: + var config: Config = Config.new() + config.viewer_url = _viewer_url + config.can_link_external_content = _can_link_external_content + return config + + + static func from_json(d: Dictionary) -> Config: + var result: Config = Config.new() + if d.get("viewer_url", null) != null: + result.viewer_url = d["viewer_url"] + if d.get("can_link_external_content", null) != null: + result.can_link_external_content = d["can_link_external_content"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_extension.gd.uid b/addons/twitcher/generated/twitch_extension.gd.uid new file mode 100644 index 00000000..b2abf730 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension.gd.uid @@ -0,0 +1 @@ +uid://djhrkne5pduls diff --git a/addons/twitcher/generated/twitch_extension_analytics.gd b/addons/twitcher/generated/twitch_extension_analytics.gd new file mode 100644 index 00000000..55c8875b --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_analytics.gd @@ -0,0 +1,93 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionAnalytics +class_name TwitchExtensionAnalytics + +## An ID that identifies the extension that the report was generated for. +@export var extension_id: String: + set(val): + extension_id = val + track_data(&"extension_id", val) + +## The URL that you use to download the report. The URL is valid for 5 minutes. +@export var URL: String: + set(val): + URL = val + track_data(&"URL", val) + +## The type of report. +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The reporting window’s start and end dates, in RFC3339 format. +@export var date_range: DateRange: + set(val): + date_range = val + track_data(&"date_range", val) + + + +## Constructor with all required fields. +static func create(_extension_id: String, _URL: String, _type: String, _date_range: DateRange) -> TwitchExtensionAnalytics: + var twitch_extension_analytics: TwitchExtensionAnalytics = TwitchExtensionAnalytics.new() + twitch_extension_analytics.extension_id = _extension_id + twitch_extension_analytics.URL = _URL + twitch_extension_analytics.type = _type + twitch_extension_analytics.date_range = _date_range + return twitch_extension_analytics + + +static func from_json(d: Dictionary) -> TwitchExtensionAnalytics: + var result: TwitchExtensionAnalytics = TwitchExtensionAnalytics.new() + if d.get("extension_id", null) != null: + result.extension_id = d["extension_id"] + if d.get("URL", null) != null: + result.URL = d["URL"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("date_range", null) != null: + result.date_range = DateRange.from_json(d["date_range"]) + return result + + + +## The reporting window’s start and end dates, in RFC3339 format. +## #/components/schemas/ExtensionAnalytics/DateRange +class DateRange extends TwitchData: + + ## The reporting window’s start date. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The reporting window’s end date. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + + + ## Constructor with all required fields. + static func create(_started_at: String, _ended_at: String) -> DateRange: + var date_range: DateRange = DateRange.new() + date_range.started_at = _started_at + date_range.ended_at = _ended_at + return date_range + + + static func from_json(d: Dictionary) -> DateRange: + var result: DateRange = DateRange.new() + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_extension_analytics.gd.uid b/addons/twitcher/generated/twitch_extension_analytics.gd.uid new file mode 100644 index 00000000..44838a7e --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_analytics.gd.uid @@ -0,0 +1 @@ +uid://dqmnnsrlinxyu diff --git a/addons/twitcher/generated/twitch_extension_bits_product.gd b/addons/twitcher/generated/twitch_extension_bits_product.gd new file mode 100644 index 00000000..abef154a --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_bits_product.gd @@ -0,0 +1,113 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionBitsProduct +class_name TwitchExtensionBitsProduct + +## The product's SKU. The SKU is unique across an extension's products. +@export var sku: String: + set(val): + sku = val + track_data(&"sku", val) + +## An object that contains the product's cost information. +@export var cost: Cost: + set(val): + cost = val + track_data(&"cost", val) + +## A Boolean value that indicates whether the product is in development. If **true**, the product is not available for public use. +@export var in_development: bool: + set(val): + in_development = val + track_data(&"in_development", val) + +## The product's name as displayed in the extension. +@export var display_name: String: + set(val): + display_name = val + track_data(&"display_name", val) + +## The date and time, in RFC3339 format, when the product expires. +@export var expiration: String: + set(val): + expiration = val + track_data(&"expiration", val) + +## A Boolean value that determines whether Bits product purchase events are broadcast to all instances of an extension on a channel. The events are broadcast via the `onTransactionComplete` helper callback. Is **true** if the event is broadcast to all instances. +@export var is_broadcast: bool: + set(val): + is_broadcast = val + track_data(&"is_broadcast", val) + + + +## Constructor with all required fields. +static func create(_sku: String, _cost: Cost, _in_development: bool, _display_name: String, _expiration: String, _is_broadcast: bool) -> TwitchExtensionBitsProduct: + var twitch_extension_bits_product: TwitchExtensionBitsProduct = TwitchExtensionBitsProduct.new() + twitch_extension_bits_product.sku = _sku + twitch_extension_bits_product.cost = _cost + twitch_extension_bits_product.in_development = _in_development + twitch_extension_bits_product.display_name = _display_name + twitch_extension_bits_product.expiration = _expiration + twitch_extension_bits_product.is_broadcast = _is_broadcast + return twitch_extension_bits_product + + +static func from_json(d: Dictionary) -> TwitchExtensionBitsProduct: + var result: TwitchExtensionBitsProduct = TwitchExtensionBitsProduct.new() + if d.get("sku", null) != null: + result.sku = d["sku"] + if d.get("cost", null) != null: + result.cost = Cost.from_json(d["cost"]) + if d.get("in_development", null) != null: + result.in_development = d["in_development"] + if d.get("display_name", null) != null: + result.display_name = d["display_name"] + if d.get("expiration", null) != null: + result.expiration = d["expiration"] + if d.get("is_broadcast", null) != null: + result.is_broadcast = d["is_broadcast"] + return result + + + +## An object that contains the product's cost information. +## #/components/schemas/ExtensionBitsProduct/Cost +class Cost extends TwitchData: + + ## The product's price. + @export var amount: int: + set(val): + amount = val + track_data(&"amount", val) + + ## The type of currency. Possible values are: + ## + ## * bits + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + + + ## Constructor with all required fields. + static func create(_amount: int, _type: String) -> Cost: + var cost: Cost = Cost.new() + cost.amount = _amount + cost.type = _type + return cost + + + static func from_json(d: Dictionary) -> Cost: + var result: Cost = Cost.new() + if d.get("amount", null) != null: + result.amount = d["amount"] + if d.get("type", null) != null: + result.type = d["type"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_extension_bits_product.gd.uid b/addons/twitcher/generated/twitch_extension_bits_product.gd.uid new file mode 100644 index 00000000..bb1d76fa --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_bits_product.gd.uid @@ -0,0 +1 @@ +uid://bagxroa6i57mf diff --git a/addons/twitcher/generated/twitch_extension_configuration_segment.gd b/addons/twitcher/generated/twitch_extension_configuration_segment.gd new file mode 100644 index 00000000..0ab1cd7e --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_configuration_segment.gd @@ -0,0 +1,59 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionConfigurationSegment +class_name TwitchExtensionConfigurationSegment + +## The type of segment. Possible values are: +## +## * broadcaster +## * developer +## * global +@export var segment: String: + set(val): + segment = val + track_data(&"segment", val) + +## The ID of the broadcaster that installed the extension. The object includes this field only if the `segment` query parameter is set to developer or broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The contents of the segment. This string may be a plain-text string or a string-encoded JSON object. +@export var content: String: + set(val): + content = val + track_data(&"content", val) + +## The version number that identifies this definition of the segment’s data. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + + + +## Constructor with all required fields. +static func create(_segment: String, _content: String, _version: String) -> TwitchExtensionConfigurationSegment: + var twitch_extension_configuration_segment: TwitchExtensionConfigurationSegment = TwitchExtensionConfigurationSegment.new() + twitch_extension_configuration_segment.segment = _segment + twitch_extension_configuration_segment.content = _content + twitch_extension_configuration_segment.version = _version + return twitch_extension_configuration_segment + + +static func from_json(d: Dictionary) -> TwitchExtensionConfigurationSegment: + var result: TwitchExtensionConfigurationSegment = TwitchExtensionConfigurationSegment.new() + if d.get("segment", null) != null: + result.segment = d["segment"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("content", null) != null: + result.content = d["content"] + if d.get("version", null) != null: + result.version = d["version"] + return result diff --git a/addons/twitcher/generated/twitch_extension_configuration_segment.gd.uid b/addons/twitcher/generated/twitch_extension_configuration_segment.gd.uid new file mode 100644 index 00000000..5e503ce6 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_configuration_segment.gd.uid @@ -0,0 +1 @@ +uid://kd31s5uklh71 diff --git a/addons/twitcher/generated/twitch_extension_icon_urls.gd b/addons/twitcher/generated/twitch_extension_icon_urls.gd new file mode 100644 index 00000000..99d54cce --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_icon_urls.gd @@ -0,0 +1,44 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionIconUrls +class_name TwitchExtensionIconUrls + +## +@export var _100x100: String: + set(val): + _100x100 = val + track_data(&"_100x100", val) + +## +@export var _24x24: String: + set(val): + _24x24 = val + track_data(&"_24x24", val) + +## +@export var _300x200: String: + set(val): + _300x200 = val + track_data(&"_300x200", val) + + + +## Constructor with all required fields. +static func create() -> TwitchExtensionIconUrls: + var twitch_extension_icon_urls: TwitchExtensionIconUrls = TwitchExtensionIconUrls.new() + return twitch_extension_icon_urls + + +static func from_json(d: Dictionary) -> TwitchExtensionIconUrls: + var result: TwitchExtensionIconUrls = TwitchExtensionIconUrls.new() + if d.get("_100x100", null) != null: + result._100x100 = d["_100x100"] + if d.get("_24x24", null) != null: + result._24x24 = d["_24x24"] + if d.get("_300x200", null) != null: + result._300x200 = d["_300x200"] + return result diff --git a/addons/twitcher/generated/twitch_extension_icon_urls.gd.uid b/addons/twitcher/generated/twitch_extension_icon_urls.gd.uid new file mode 100644 index 00000000..1cda716f --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_icon_urls.gd.uid @@ -0,0 +1 @@ +uid://cmoojllshgvh0 diff --git a/addons/twitcher/generated/twitch_extension_live_channel.gd b/addons/twitcher/generated/twitch_extension_live_channel.gd new file mode 100644 index 00000000..7932aaed --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_live_channel.gd @@ -0,0 +1,65 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionLiveChannel +class_name TwitchExtensionLiveChannel + +## The ID of the broadcaster that is streaming live and has installed or activated the extension. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The name of the category or game being streamed. +@export var game_name: String: + set(val): + game_name = val + track_data(&"game_name", val) + +## The ID of the category or game being streamed. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The title of the broadcaster’s stream. May be an empty string if not specified. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_name: String, _game_name: String, _game_id: String, _title: String) -> TwitchExtensionLiveChannel: + var twitch_extension_live_channel: TwitchExtensionLiveChannel = TwitchExtensionLiveChannel.new() + twitch_extension_live_channel.broadcaster_id = _broadcaster_id + twitch_extension_live_channel.broadcaster_name = _broadcaster_name + twitch_extension_live_channel.game_name = _game_name + twitch_extension_live_channel.game_id = _game_id + twitch_extension_live_channel.title = _title + return twitch_extension_live_channel + + +static func from_json(d: Dictionary) -> TwitchExtensionLiveChannel: + var result: TwitchExtensionLiveChannel = TwitchExtensionLiveChannel.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("game_name", null) != null: + result.game_name = d["game_name"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("title", null) != null: + result.title = d["title"] + return result diff --git a/addons/twitcher/generated/twitch_extension_live_channel.gd.uid b/addons/twitcher/generated/twitch_extension_live_channel.gd.uid new file mode 100644 index 00000000..96d1157e --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_live_channel.gd.uid @@ -0,0 +1 @@ +uid://cvmneq8w38gjn diff --git a/addons/twitcher/generated/twitch_extension_secret.gd b/addons/twitcher/generated/twitch_extension_secret.gd new file mode 100644 index 00000000..b55fef36 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_secret.gd @@ -0,0 +1,85 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionSecret +class_name TwitchExtensionSecret + +## The version number that identifies this definition of the secret’s data. +@export var format_version: int: + set(val): + format_version = val + track_data(&"format_version", val) + +## The list of secrets. +@export var secrets: Array[Secrets]: + set(val): + secrets = val + track_data(&"secrets", val) + + + +## Constructor with all required fields. +static func create(_format_version: int, _secrets: Array[Secrets]) -> TwitchExtensionSecret: + var twitch_extension_secret: TwitchExtensionSecret = TwitchExtensionSecret.new() + twitch_extension_secret.format_version = _format_version + twitch_extension_secret.secrets = _secrets + return twitch_extension_secret + + +static func from_json(d: Dictionary) -> TwitchExtensionSecret: + var result: TwitchExtensionSecret = TwitchExtensionSecret.new() + if d.get("format_version", null) != null: + result.format_version = d["format_version"] + if d.get("secrets", null) != null: + for value in d["secrets"]: + result.secrets.append(Secrets.from_json(value)) + return result + + + +## The list of secrets. +## #/components/schemas/ExtensionSecret/Secrets +class Secrets extends TwitchData: + + ## The raw secret that you use with JWT encoding. + @export var content: String: + set(val): + content = val + track_data(&"content", val) + + ## The UTC date and time (in RFC3339 format) that you may begin using this secret to sign a JWT. + @export var active_at: String: + set(val): + active_at = val + track_data(&"active_at", val) + + ## The UTC date and time (in RFC3339 format) that you must stop using this secret to decode a JWT. + @export var expires_at: String: + set(val): + expires_at = val + track_data(&"expires_at", val) + + + + ## Constructor with all required fields. + static func create(_content: String, _active_at: String, _expires_at: String) -> Secrets: + var secrets: Secrets = Secrets.new() + secrets.content = _content + secrets.active_at = _active_at + secrets.expires_at = _expires_at + return secrets + + + static func from_json(d: Dictionary) -> Secrets: + var result: Secrets = Secrets.new() + if d.get("content", null) != null: + result.content = d["content"] + if d.get("active_at", null) != null: + result.active_at = d["active_at"] + if d.get("expires_at", null) != null: + result.expires_at = d["expires_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_extension_secret.gd.uid b/addons/twitcher/generated/twitch_extension_secret.gd.uid new file mode 100644 index 00000000..68aa3b83 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_secret.gd.uid @@ -0,0 +1 @@ +uid://eqe680mdiuyy diff --git a/addons/twitcher/generated/twitch_extension_transaction.gd b/addons/twitcher/generated/twitch_extension_transaction.gd new file mode 100644 index 00000000..a54dae34 --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_transaction.gd @@ -0,0 +1,232 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/ExtensionTransaction +class_name TwitchExtensionTransaction + +## An ID that identifies the transaction. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The UTC date and time (in RFC3339 format) of the transaction. +@export var timestamp: String: + set(val): + timestamp = val + track_data(&"timestamp", val) + +## The ID of the broadcaster that owns the channel where the transaction occurred. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The ID of the user that purchased the digital product. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The type of transaction. Possible values are: +## +## * BITS\_IN\_EXTENSION +@export var product_type: String: + set(val): + product_type = val + track_data(&"product_type", val) + +## Contains details about the digital product. +@export var product_data: ProductData: + set(val): + product_data = val + track_data(&"product_data", val) + + + +## Constructor with all required fields. +static func create(_id: String, _timestamp: String, _broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _user_id: String, _user_login: String, _user_name: String, _product_type: String, _product_data: ProductData) -> TwitchExtensionTransaction: + var twitch_extension_transaction: TwitchExtensionTransaction = TwitchExtensionTransaction.new() + twitch_extension_transaction.id = _id + twitch_extension_transaction.timestamp = _timestamp + twitch_extension_transaction.broadcaster_id = _broadcaster_id + twitch_extension_transaction.broadcaster_login = _broadcaster_login + twitch_extension_transaction.broadcaster_name = _broadcaster_name + twitch_extension_transaction.user_id = _user_id + twitch_extension_transaction.user_login = _user_login + twitch_extension_transaction.user_name = _user_name + twitch_extension_transaction.product_type = _product_type + twitch_extension_transaction.product_data = _product_data + return twitch_extension_transaction + + +static func from_json(d: Dictionary) -> TwitchExtensionTransaction: + var result: TwitchExtensionTransaction = TwitchExtensionTransaction.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("timestamp", null) != null: + result.timestamp = d["timestamp"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("product_type", null) != null: + result.product_type = d["product_type"] + if d.get("product_data", null) != null: + result.product_data = ProductData.from_json(d["product_data"]) + return result + + + +## Contains details about the digital product. +## #/components/schemas/ExtensionTransaction/ProductData +class ProductData extends TwitchData: + + ## An ID that identifies the digital product. + @export var sku: String: + set(val): + sku = val + track_data(&"sku", val) + + ## Set to `twitch.ext.` \+ ``. + @export var domain: String: + set(val): + domain = val + track_data(&"domain", val) + + ## Contains details about the digital product’s cost. + @export var cost: Cost: + set(val): + cost = val + track_data(&"cost", val) + + ## A Boolean value that determines whether the product is in development. Is **true** if the digital product is in development and cannot be exchanged. + @export var inDevelopment: bool: + set(val): + inDevelopment = val + track_data(&"inDevelopment", val) + + ## The name of the digital product. + @export var displayName: String: + set(val): + displayName = val + track_data(&"displayName", val) + + ## This field is always empty since you may purchase only unexpired products. + @export var expiration: String: + set(val): + expiration = val + track_data(&"expiration", val) + + ## A Boolean value that determines whether the data was broadcast to all instances of the extension. Is **true** if the data was broadcast to all instances. + @export var broadcast: bool: + set(val): + broadcast = val + track_data(&"broadcast", val) + + + + ## Constructor with all required fields. + static func create(_sku: String, _domain: String, _cost: Cost, _inDevelopment: bool, _displayName: String, _expiration: String, _broadcast: bool) -> ProductData: + var product_data: ProductData = ProductData.new() + product_data.sku = _sku + product_data.domain = _domain + product_data.cost = _cost + product_data.inDevelopment = _inDevelopment + product_data.displayName = _displayName + product_data.expiration = _expiration + product_data.broadcast = _broadcast + return product_data + + + static func from_json(d: Dictionary) -> ProductData: + var result: ProductData = ProductData.new() + if d.get("sku", null) != null: + result.sku = d["sku"] + if d.get("domain", null) != null: + result.domain = d["domain"] + if d.get("cost", null) != null: + result.cost = Cost.from_json(d["cost"]) + if d.get("inDevelopment", null) != null: + result.inDevelopment = d["inDevelopment"] + if d.get("displayName", null) != null: + result.displayName = d["displayName"] + if d.get("expiration", null) != null: + result.expiration = d["expiration"] + if d.get("broadcast", null) != null: + result.broadcast = d["broadcast"] + return result + + + +## Contains details about the digital product’s cost. +## #/components/schemas/ExtensionTransaction/ProductData/Cost +class Cost extends TwitchData: + + ## The amount exchanged for the digital product. + @export var amount: int: + set(val): + amount = val + track_data(&"amount", val) + + ## The type of currency exchanged. Possible values are: + ## + ## * bits + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + + + ## Constructor with all required fields. + static func create(_amount: int, _type: String) -> Cost: + var cost: Cost = Cost.new() + cost.amount = _amount + cost.type = _type + return cost + + + static func from_json(d: Dictionary) -> Cost: + var result: Cost = Cost.new() + if d.get("amount", null) != null: + result.amount = d["amount"] + if d.get("type", null) != null: + result.type = d["type"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_extension_transaction.gd.uid b/addons/twitcher/generated/twitch_extension_transaction.gd.uid new file mode 100644 index 00000000..500f316d --- /dev/null +++ b/addons/twitcher/generated/twitch_extension_transaction.gd.uid @@ -0,0 +1 @@ +uid://bq87rulgk036l diff --git a/addons/twitcher/generated/twitch_game.gd b/addons/twitcher/generated/twitch_game.gd new file mode 100644 index 00000000..4977d931 --- /dev/null +++ b/addons/twitcher/generated/twitch_game.gd @@ -0,0 +1,56 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Game +class_name TwitchGame + +## An ID that identifies the category or game. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The category’s or game’s name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## A URL to the category’s or game’s box art. You must replace the `{width}x{height}` placeholder with the size of image you want. +@export var box_art_url: String: + set(val): + box_art_url = val + track_data(&"box_art_url", val) + +## The ID that [IGDB](https://www.igdb.com/) uses to identify this game. If the IGDB ID is not available to Twitch, this field is set to an empty string. +@export var igdb_id: String: + set(val): + igdb_id = val + track_data(&"igdb_id", val) + + + +## Constructor with all required fields. +static func create(_id: String, _name: String, _box_art_url: String, _igdb_id: String) -> TwitchGame: + var twitch_game: TwitchGame = TwitchGame.new() + twitch_game.id = _id + twitch_game.name = _name + twitch_game.box_art_url = _box_art_url + twitch_game.igdb_id = _igdb_id + return twitch_game + + +static func from_json(d: Dictionary) -> TwitchGame: + var result: TwitchGame = TwitchGame.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("box_art_url", null) != null: + result.box_art_url = d["box_art_url"] + if d.get("igdb_id", null) != null: + result.igdb_id = d["igdb_id"] + return result diff --git a/addons/twitcher/generated/twitch_game.gd.uid b/addons/twitcher/generated/twitch_game.gd.uid new file mode 100644 index 00000000..4af3a391 --- /dev/null +++ b/addons/twitcher/generated/twitch_game.gd.uid @@ -0,0 +1 @@ +uid://dd12ryhhkmvss diff --git a/addons/twitcher/generated/twitch_game_analytics.gd b/addons/twitcher/generated/twitch_game_analytics.gd new file mode 100644 index 00000000..cf675f2f --- /dev/null +++ b/addons/twitcher/generated/twitch_game_analytics.gd @@ -0,0 +1,93 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/GameAnalytics +class_name TwitchGameAnalytics + +## An ID that identifies the game that the report was generated for. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The URL that you use to download the report. The URL is valid for 5 minutes. +@export var URL: String: + set(val): + URL = val + track_data(&"URL", val) + +## The type of report. +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The reporting window’s start and end dates, in RFC3339 format. +@export var date_range: DateRange: + set(val): + date_range = val + track_data(&"date_range", val) + + + +## Constructor with all required fields. +static func create(_game_id: String, _URL: String, _type: String, _date_range: DateRange) -> TwitchGameAnalytics: + var twitch_game_analytics: TwitchGameAnalytics = TwitchGameAnalytics.new() + twitch_game_analytics.game_id = _game_id + twitch_game_analytics.URL = _URL + twitch_game_analytics.type = _type + twitch_game_analytics.date_range = _date_range + return twitch_game_analytics + + +static func from_json(d: Dictionary) -> TwitchGameAnalytics: + var result: TwitchGameAnalytics = TwitchGameAnalytics.new() + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("URL", null) != null: + result.URL = d["URL"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("date_range", null) != null: + result.date_range = DateRange.from_json(d["date_range"]) + return result + + + +## The reporting window’s start and end dates, in RFC3339 format. +## #/components/schemas/GameAnalytics/DateRange +class DateRange extends TwitchData: + + ## The reporting window’s start date. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The reporting window’s end date. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + + + ## Constructor with all required fields. + static func create(_started_at: String, _ended_at: String) -> DateRange: + var date_range: DateRange = DateRange.new() + date_range.started_at = _started_at + date_range.ended_at = _ended_at + return date_range + + + static func from_json(d: Dictionary) -> DateRange: + var result: DateRange = DateRange.new() + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_game_analytics.gd.uid b/addons/twitcher/generated/twitch_game_analytics.gd.uid new file mode 100644 index 00000000..5a4b5c8e --- /dev/null +++ b/addons/twitcher/generated/twitch_game_analytics.gd.uid @@ -0,0 +1 @@ +uid://bug7muidumlkf diff --git a/addons/twitcher/generated/twitch_get_ad_schedule.gd b/addons/twitcher/generated/twitch_get_ad_schedule.gd new file mode 100644 index 00000000..834f0541 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_ad_schedule.gd @@ -0,0 +1,107 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetAdSchedule + + + +## +## #/components/schemas/GetAdScheduleResponse +class Response extends TwitchData: + + ## A list that contains information related to the channel’s ad schedule. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains information related to the channel’s ad schedule. +## #/components/schemas/GetAdScheduleResponse/Data +class ResponseData extends TwitchData: + + ## The number of snoozes available for the broadcaster. + @export var snooze_count: int: + set(val): + snooze_count = val + track_data(&"snooze_count", val) + + ## The UTC timestamp when the broadcaster will gain an additional snooze, in RFC3339 format. + @export var snooze_refresh_at: String: + set(val): + snooze_refresh_at = val + track_data(&"snooze_refresh_at", val) + + ## The UTC timestamp of the broadcaster’s next scheduled ad, in RFC3339 format. Empty if the channel has no ad scheduled or is not live. + @export var next_ad_at: String: + set(val): + next_ad_at = val + track_data(&"next_ad_at", val) + + ## The length in seconds of the scheduled upcoming ad break. + @export var duration: int: + set(val): + duration = val + track_data(&"duration", val) + + ## The UTC timestamp of the broadcaster’s last ad-break, in RFC3339 format. Empty if the channel has not run an ad or is not live. + @export var last_ad_at: String: + set(val): + last_ad_at = val + track_data(&"last_ad_at", val) + + ## The amount of pre-roll free time remaining for the channel in seconds. Returns 0 if they are currently not pre-roll free. + @export var preroll_free_time: int: + set(val): + preroll_free_time = val + track_data(&"preroll_free_time", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_snooze_count: int, _snooze_refresh_at: String, _next_ad_at: String, _duration: int, _last_ad_at: String, _preroll_free_time: int) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.snooze_count = _snooze_count + response_data.snooze_refresh_at = _snooze_refresh_at + response_data.next_ad_at = _next_ad_at + response_data.duration = _duration + response_data.last_ad_at = _last_ad_at + response_data.preroll_free_time = _preroll_free_time + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("snooze_count", null) != null: + result.snooze_count = d["snooze_count"] + if d.get("snooze_refresh_at", null) != null: + result.snooze_refresh_at = d["snooze_refresh_at"] + if d.get("next_ad_at", null) != null: + result.next_ad_at = d["next_ad_at"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("last_ad_at", null) != null: + result.last_ad_at = d["last_ad_at"] + if d.get("preroll_free_time", null) != null: + result.preroll_free_time = d["preroll_free_time"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_ad_schedule.gd.uid b/addons/twitcher/generated/twitch_get_ad_schedule.gd.uid new file mode 100644 index 00000000..cdaf2426 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_ad_schedule.gd.uid @@ -0,0 +1 @@ +uid://o7ledpu2xxqf diff --git a/addons/twitcher/generated/twitch_get_all_stream_tags.gd b/addons/twitcher/generated/twitch_get_all_stream_tags.gd new file mode 100644 index 00000000..63e57ef3 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_all_stream_tags.gd @@ -0,0 +1,153 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetAllStreamTags + + + +## +## #/components/schemas/GetAllStreamTagsResponse +class Response extends TwitchData: + + ## The list of stream tags that the broadcaster can apply to their channel. + @export var data: Array[TwitchStreamTag]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStreamTag]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStreamTag.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetAllStreamTagsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ query parameter to this value to page forwards through the results. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_all_stream_tags +## #/components/schemas/GetAllStreamTagsOpt +class Opt extends TwitchData: + + ## The ID of the tag to get. Used to filter the list of tags. To specify more than one tag, include the _tag\_id_ parameter for each tag to get. For example, `tag_id=1234&tag_id=5678`. The maximum number of IDs you may specify is 100\. Ignores invalid IDs but not duplicate IDs. + @export var tag_id: Array[String]: + set(val): + tag_id = val + track_data(&"tag_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("tag_id", null) != null: + for value in d["tag_id"]: + result.tag_id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_all_stream_tags.gd.uid b/addons/twitcher/generated/twitch_get_all_stream_tags.gd.uid new file mode 100644 index 00000000..ffb3745c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_all_stream_tags.gd.uid @@ -0,0 +1 @@ +uid://bsl361olbslsl diff --git a/addons/twitcher/generated/twitch_get_auto_mod_settings.gd b/addons/twitcher/generated/twitch_get_auto_mod_settings.gd new file mode 100644 index 00000000..4ff7ebda --- /dev/null +++ b/addons/twitcher/generated/twitch_get_auto_mod_settings.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetAutoModSettings + + + +## +## #/components/schemas/GetAutoModSettingsResponse +class Response extends TwitchData: + + ## The list of AutoMod settings. The list contains a single object that contains all the AutoMod settings. + @export var data: Array[TwitchAutoModSettings]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchAutoModSettings]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchAutoModSettings.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_auto_mod_settings.gd.uid b/addons/twitcher/generated/twitch_get_auto_mod_settings.gd.uid new file mode 100644 index 00000000..5dc09df0 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_auto_mod_settings.gd.uid @@ -0,0 +1 @@ +uid://cn0jo18gvevx4 diff --git a/addons/twitcher/generated/twitch_get_banned_users.gd b/addons/twitcher/generated/twitch_get_banned_users.gd new file mode 100644 index 00000000..28a239b6 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_banned_users.gd @@ -0,0 +1,163 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetBannedUsers + + + +## +## #/components/schemas/GetBannedUsersResponse +class Response extends TwitchData: + + ## The list of users that were banned or put in a timeout. + @export var data: Array[TwitchBannedUser]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchBannedUser]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchBannedUser.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetBannedUsersResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_banned_users +## #/components/schemas/GetBannedUsersOpt +class Opt extends TwitchData: + + ## A list of user IDs used to filter the results. To specify more than one ID, include this parameter for each user you want to get. For example, `user_id=1234&user_id=5678`. You may specify a maximum of 100 IDs. + ## + ## The returned list includes only those users that were banned or put in a timeout. The list is returned in the same order that you specified the IDs. + @export var user_id: Array[String]: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + for value in d["user_id"]: + result.user_id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("before", null) != null: + result.before = d["before"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_banned_users.gd.uid b/addons/twitcher/generated/twitch_get_banned_users.gd.uid new file mode 100644 index 00000000..247f532d --- /dev/null +++ b/addons/twitcher/generated/twitch_get_banned_users.gd.uid @@ -0,0 +1 @@ +uid://b8drtddq3tkto diff --git a/addons/twitcher/generated/twitch_get_bits_leaderboard.gd b/addons/twitcher/generated/twitch_get_bits_leaderboard.gd new file mode 100644 index 00000000..a09802e2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_bits_leaderboard.gd @@ -0,0 +1,149 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetBitsLeaderboard + + + +## +## #/components/schemas/GetBitsLeaderboardResponse +class Response extends TwitchData: + + ## A list of leaderboard leaders. The leaders are returned in rank order by how much they’ve cheered. The array is empty if nobody has cheered bits. + @export var data: Array[TwitchBitsLeaderboard]: + set(val): + data = val + track_data(&"data", val) + + ## The reporting window’s start and end dates, in RFC3339 format. The dates are calculated by using the _started\_at_ and _period_ query parameters. If you don’t specify the _started\_at_ query parameter, the fields contain empty strings. + @export var date_range: ResponseDateRange: + set(val): + date_range = val + track_data(&"date_range", val) + + ## The number of ranked users in `data`. This is the value in the _count_ query parameter or the total number of entries on the leaderboard, whichever is less. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchBitsLeaderboard], _date_range: ResponseDateRange, _total: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.date_range = _date_range + response.total = _total + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchBitsLeaderboard.from_json(value)) + if d.get("date_range", null) != null: + result.date_range = ResponseDateRange.from_json(d["date_range"]) + if d.get("total", null) != null: + result.total = d["total"] + return result + + + +## The reporting window’s start and end dates, in RFC3339 format. The dates are calculated by using the _started\_at_ and _period_ query parameters. If you don’t specify the _started\_at_ query parameter, the fields contain empty strings. +## #/components/schemas/GetBitsLeaderboardResponse/DateRange +class ResponseDateRange extends TwitchData: + + ## The reporting window’s start date. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The reporting window’s end date. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_started_at: String, _ended_at: String) -> ResponseDateRange: + var response_date_range: ResponseDateRange = ResponseDateRange.new() + response_date_range.started_at = _started_at + response_date_range.ended_at = _ended_at + return response_date_range + + + static func from_json(d: Dictionary) -> ResponseDateRange: + var result: ResponseDateRange = ResponseDateRange.new() + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + return result + + + +## All optional parameters for TwitchAPI.get_bits_leaderboard +## #/components/schemas/GetBitsLeaderboardOpt +class Opt extends TwitchData: + + ## The number of results to return. The minimum count is 1 and the maximum is 100\. The default is 10. + @export var count: int: + set(val): + count = val + track_data(&"count", val) + + ## The time period over which data is aggregated (uses the PST time zone). Possible values are: + ## + ## * day — A day spans from 00:00:00 on the day specified in _started\_at_ and runs through 00:00:00 of the next day. + ## * week — A week spans from 00:00:00 on the Monday of the week specified in _started\_at_ and runs through 00:00:00 of the next Monday. + ## * month — A month spans from 00:00:00 on the first day of the month specified in _started\_at_ and runs through 00:00:00 of the first day of the next month. + ## * year — A year spans from 00:00:00 on the first day of the year specified in _started\_at_ and runs through 00:00:00 of the first day of the next year. + ## * all — Default. The lifetime of the broadcaster's channel. + @export var period: String: + set(val): + period = val + track_data(&"period", val) + + ## The start date, in RFC3339 format, used for determining the aggregation period. Specify this parameter only if you specify the _period_ query parameter. The start date is ignored if _period_ is all. + ## + ## Note that the date is converted to PST before being used, so if you set the start time to `2022-01-01T00:00:00.0Z` and _period_ to month, the actual reporting period is December 2021, not January 2022\. If you want the reporting period to be January 2022, you must set the start time to `2022-01-01T08:00:00.0Z` or `2022-01-01T00:00:00.0-08:00`. + ## + ## If your start date uses the ‘+’ offset operator (for example, `2022-01-01T00:00:00.0+05:00`), you must URL encode the start date. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## An ID that identifies a user that cheered bits in the channel. If _count_ is greater than 1, the response may include users ranked above and below the specified user. To get the leaderboard’s top leaders, don’t specify a user ID. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("count", null) != null: + result.count = d["count"] + if d.get("period", null) != null: + result.period = d["period"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_bits_leaderboard.gd.uid b/addons/twitcher/generated/twitch_get_bits_leaderboard.gd.uid new file mode 100644 index 00000000..a100b35b --- /dev/null +++ b/addons/twitcher/generated/twitch_get_bits_leaderboard.gd.uid @@ -0,0 +1 @@ +uid://csg85msecj0tg diff --git a/addons/twitcher/generated/twitch_get_blocked_terms.gd b/addons/twitcher/generated/twitch_get_blocked_terms.gd new file mode 100644 index 00000000..7c949dd6 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_blocked_terms.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetBlockedTerms + + + +## +## #/components/schemas/GetBlockedTermsResponse +class Response extends TwitchData: + + ## The list of blocked terms. The list is in descending order of when they were created (see the `created_at` timestamp). + @export var data: Array[TwitchBlockedTerm]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchBlockedTerm]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchBlockedTerm.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetBlockedTermsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_blocked_terms +## #/components/schemas/GetBlockedTermsOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_blocked_terms.gd.uid b/addons/twitcher/generated/twitch_get_blocked_terms.gd.uid new file mode 100644 index 00000000..f17617e9 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_blocked_terms.gd.uid @@ -0,0 +1 @@ +uid://bia4e0g578tfj diff --git a/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd new file mode 100644 index 00000000..b1cf953c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd @@ -0,0 +1,181 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetBroadcasterSubscriptions + + + +## +## #/components/schemas/GetBroadcasterSubscriptionsResponse +class Response extends TwitchData: + + ## The list of users that subscribe to the broadcaster. The list is empty if the broadcaster has no subscribers. + @export var data: Array[TwitchBroadcasterSubscription]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + + ## The current number of subscriber points earned by this broadcaster. Points are based on the subscription tier of each user that subscribes to this broadcaster. For example, a Tier 1 subscription is worth 1 point, Tier 2 is worth 2 points, and Tier 3 is worth 6 points. The number of points determines the number of emote slots that are unlocked for the broadcaster (see [Subscriber Emote Slots](https://help.twitch.tv/s/article/subscriber-emote-guide#emoteslots)). + @export var points: int: + set(val): + points = val + track_data(&"points", val) + + ## The total number of users that subscribe to this broadcaster. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchBroadcasterSubscription], _points: int, _total: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.points = _points + response.total = _total + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchBroadcasterSubscription.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + if d.get("points", null) != null: + result.points = d["points"] + if d.get("total", null) != null: + result.total = d["total"] + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + points = response.points + total = response.total + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetBroadcasterSubscriptionsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next or previous page of results. Use the cursor to set the request’s _after_ or _before_ query parameter depending on whether you’re paging forwards or backwards. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_broadcaster_subscriptions +## #/components/schemas/GetBroadcasterSubscriptionsOpt +class Opt extends TwitchData: + + ## Filters the list to include only the specified subscribers. To specify more than one subscriber, include this parameter for each subscriber. For example, `&user_id=1234&user_id=5678`. You may specify a maximum of 100 subscribers. + @export var user_id: Array[String]: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. Do not specify if you set the _user\_id_ query parameter. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The cursor used to get the previous page of results. Do not specify if you set the _user\_id_ query parameter. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + for value in d["user_id"]: + result.user_id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("before", null) != null: + result.before = d["before"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd.uid b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd.uid new file mode 100644 index 00000000..e93f1eab --- /dev/null +++ b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd.uid @@ -0,0 +1 @@ +uid://cxdka5b8313py diff --git a/addons/twitcher/generated/twitch_get_channel_chat_badges.gd b/addons/twitcher/generated/twitch_get_channel_chat_badges.gd new file mode 100644 index 00000000..ccaeb6c8 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_chat_badges.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelChatBadges + + + +## +## #/components/schemas/GetChannelChatBadgesResponse +class Response extends TwitchData: + + ## The list of chat badges. The list is sorted in ascending order by `set_id`, and within a set, the list is sorted in ascending order by `id`. + @export var data: Array[TwitchChatBadge]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChatBadge]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChatBadge.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_chat_badges.gd.uid b/addons/twitcher/generated/twitch_get_channel_chat_badges.gd.uid new file mode 100644 index 00000000..69913b3d --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_chat_badges.gd.uid @@ -0,0 +1 @@ +uid://ccfuby1siu65r diff --git a/addons/twitcher/generated/twitch_get_channel_editors.gd b/addons/twitcher/generated/twitch_get_channel_editors.gd new file mode 100644 index 00000000..fcb1713f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_editors.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelEditors + + + +## +## #/components/schemas/GetChannelEditorsResponse +class Response extends TwitchData: + + ## A list of users that are editors for the specified broadcaster. The list is empty if the broadcaster doesn’t have editors. + @export var data: Array[TwitchChannelEditor]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChannelEditor]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChannelEditor.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_editors.gd.uid b/addons/twitcher/generated/twitch_get_channel_editors.gd.uid new file mode 100644 index 00000000..685ff297 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_editors.gd.uid @@ -0,0 +1 @@ +uid://bee0orkp8th2t diff --git a/addons/twitcher/generated/twitch_get_channel_emotes.gd b/addons/twitcher/generated/twitch_get_channel_emotes.gd new file mode 100644 index 00000000..a20a5850 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_emotes.gd @@ -0,0 +1,44 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelEmotes + + + +## +## #/components/schemas/GetChannelEmotesResponse +class Response extends TwitchData: + + ## The list of emotes that the specified broadcaster created. If the broadcaster hasn't created custom emotes, the list is empty. + @export var data: Array[TwitchChannelEmote]: + set(val): + data = val + track_data(&"data", val) + + ## A templated URL. Use the values from the `id`, `format`, `scale`, and `theme_mode` fields to replace the like-named placeholder strings in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. For information about what the template looks like and how to use it to fetch emotes, see [Emote CDN URL format](https://dev.twitch.tv/docs/irc/emotes#cdn-template). You should use this template instead of using the URLs in the `images` object. + @export var template: String: + set(val): + template = val + track_data(&"template", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChannelEmote], _template: String) -> Response: + var response: Response = Response.new() + response.data = _data + response.template = _template + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChannelEmote.from_json(value)) + if d.get("template", null) != null: + result.template = d["template"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_emotes.gd.uid b/addons/twitcher/generated/twitch_get_channel_emotes.gd.uid new file mode 100644 index 00000000..af9cc83c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_emotes.gd.uid @@ -0,0 +1 @@ +uid://bfkf6a371k7ys diff --git a/addons/twitcher/generated/twitch_get_channel_followers.gd b/addons/twitcher/generated/twitch_get_channel_followers.gd new file mode 100644 index 00000000..6f06e2ef --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_followers.gd @@ -0,0 +1,218 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelFollowers + + + +## +## #/components/schemas/GetChannelFollowersResponse +class Response extends TwitchData: + + ## The list of users that follow the specified broadcaster. The list is in descending order by `followed_at` (with the most recent follower first). The list is empty if nobody follows the broadcaster, the specified `user_id` isn’t in the follower list, the user access token is missing the **moderator:read:followers** scope, or the user isn’t the broadcaster or moderator for the channel. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + + ## The total number of users that follow this broadcaster. As someone pages through the list, the number of users may change as users follow or unfollow the broadcaster. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData], _total: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.total = _total + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + if d.get("total", null) != null: + result.total = d["total"] + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + total = response.total + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The list of users that follow the specified broadcaster. The list is in descending order by `followed_at` (with the most recent follower first). The list is empty if nobody follows the broadcaster, the specified `user_id` isn’t in the follower list, the user access token is missing the **moderator:read:followers** scope, or the user isn’t the broadcaster or moderator for the channel. +## #/components/schemas/GetChannelFollowersResponse/Data +class ResponseData extends TwitchData: + + ## The UTC timestamp when the user started following the broadcaster. + @export var followed_at: String: + set(val): + followed_at = val + track_data(&"followed_at", val) + + ## An ID that uniquely identifies the user that’s following the broadcaster. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The user’s login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The user’s display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_followed_at: String, _user_id: String, _user_login: String, _user_name: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.followed_at = _followed_at + response_data.user_id = _user_id + response_data.user_login = _user_login + response_data.user_name = _user_name + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("followed_at", null) != null: + result.followed_at = d["followed_at"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + return result + + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). +## #/components/schemas/GetChannelFollowersResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_channel_followers +## #/components/schemas/GetChannelFollowersOpt +class Opt extends TwitchData: + + ## A user’s ID. Use this parameter to see whether the user follows this broadcaster. If specified, the response contains this user if they follow the broadcaster. If not specified, the response contains all users that follow the broadcaster. + ## + ## Using this parameter requires both a user access token with the **moderator:read:followers** scope and the user ID in the access token match the broadcaster\_id or be the user ID for a moderator of the specified broadcaster. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_followers.gd.uid b/addons/twitcher/generated/twitch_get_channel_followers.gd.uid new file mode 100644 index 00000000..d69792a6 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_followers.gd.uid @@ -0,0 +1 @@ +uid://bjqv8p6qqupvi diff --git a/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd b/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd new file mode 100644 index 00000000..88c3e212 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd @@ -0,0 +1,73 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelGuestStarSettings + + + +## +## #/components/schemas/GetChannelGuestStarSettingsResponse +class Response extends TwitchData: + + ## Flag determining if Guest Star moderators have access to control whether a guest is live once assigned to a slot. + @export var is_moderator_send_live_enabled: bool: + set(val): + is_moderator_send_live_enabled = val + track_data(&"is_moderator_send_live_enabled", val) + + ## Number of slots the Guest Star call interface will allow the host to add to a call. Required to be between 1 and 6. + @export var slot_count: int: + set(val): + slot_count = val + track_data(&"slot_count", val) + + ## Flag determining if Browser Sources subscribed to sessions on this channel should output audio + @export var is_browser_source_audio_enabled: bool: + set(val): + is_browser_source_audio_enabled = val + track_data(&"is_browser_source_audio_enabled", val) + + ## This setting determines how the guests within a session should be laid out within the browser source. Can be one of the following values: + ## + ## * `TILED_LAYOUT`: All live guests are tiled within the browser source with the same size. + ## * `SCREENSHARE_LAYOUT`: All live guests are tiled within the browser source with the same size. If there is an active screen share, it is sized larger than the other guests. + @export var group_layout: String: + set(val): + group_layout = val + track_data(&"group_layout", val) + + ## View only token to generate browser source URLs + @export var browser_source_token: String: + set(val): + browser_source_token = val + track_data(&"browser_source_token", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_is_moderator_send_live_enabled: bool, _slot_count: int, _is_browser_source_audio_enabled: bool, _group_layout: String, _browser_source_token: String) -> Response: + var response: Response = Response.new() + response.is_moderator_send_live_enabled = _is_moderator_send_live_enabled + response.slot_count = _slot_count + response.is_browser_source_audio_enabled = _is_browser_source_audio_enabled + response.group_layout = _group_layout + response.browser_source_token = _browser_source_token + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("is_moderator_send_live_enabled", null) != null: + result.is_moderator_send_live_enabled = d["is_moderator_send_live_enabled"] + if d.get("slot_count", null) != null: + result.slot_count = d["slot_count"] + if d.get("is_browser_source_audio_enabled", null) != null: + result.is_browser_source_audio_enabled = d["is_browser_source_audio_enabled"] + if d.get("group_layout", null) != null: + result.group_layout = d["group_layout"] + if d.get("browser_source_token", null) != null: + result.browser_source_token = d["browser_source_token"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd.uid b/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd.uid new file mode 100644 index 00000000..4abade38 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_guest_star_settings.gd.uid @@ -0,0 +1 @@ +uid://bysr415krbhvs diff --git a/addons/twitcher/generated/twitch_get_channel_information.gd b/addons/twitcher/generated/twitch_get_channel_information.gd new file mode 100644 index 00000000..9551596f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_information.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelInformation + + + +## +## #/components/schemas/GetChannelInformationResponse +class Response extends TwitchData: + + ## A list that contains information about the specified channels. The list is empty if the specified channels weren’t found. + @export var data: Array[TwitchChannelInformation]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChannelInformation]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChannelInformation.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_information.gd.uid b/addons/twitcher/generated/twitch_get_channel_information.gd.uid new file mode 100644 index 00000000..194834d1 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_information.gd.uid @@ -0,0 +1 @@ +uid://cxvsllsqxwp5e diff --git a/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd new file mode 100644 index 00000000..44d4a0a0 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd @@ -0,0 +1,272 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelStreamSchedule + + + +## +## #/components/schemas/GetChannelStreamScheduleResponse +class Response extends TwitchData: + + ## The broadcaster’s streaming schedule. + @export var data: ResponseData: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: ResponseData) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + result.data = ResponseData.from_json(d["data"]) + return result + + + +## The broadcaster’s streaming schedule. +## #/components/schemas/GetChannelStreamScheduleResponse/Data +class ResponseData extends TwitchData: + + ## The list of broadcasts in the broadcaster’s streaming schedule. + @export var segments: Array[TwitchChannelStreamScheduleSegment]: + set(val): + segments = val + track_data(&"segments", val) + + ## The ID of the broadcaster that owns the broadcast schedule. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster’s display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## The broadcaster’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. + @export var vacation: ResponseVacation: + set(val): + vacation = val + track_data(&"vacation", val) + + ## The information used to page through a list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_segments: Array[TwitchChannelStreamScheduleSegment], _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _vacation: ResponseVacation) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.segments = _segments + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_name = _broadcaster_name + response_data.broadcaster_login = _broadcaster_login + response_data.vacation = _vacation + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("segments", null) != null: + for value in d["segments"]: + result.segments.append(TwitchChannelStreamScheduleSegment.from_json(value)) + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("vacation", null) != null: + result.vacation = ResponseVacation.from_json(d["vacation"]) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response.data._next_page + segments = response.data.segments + broadcaster_id = response.data.broadcaster_id + broadcaster_name = response.data.broadcaster_name + broadcaster_login = response.data.broadcaster_login + vacation = response.data.vacation + pagination = response.data.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if segments.is_empty(): return false + iter[0] = segments[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if segments.size() > _cur_iter: + iter[0] = segments[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if segments.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. +## #/components/schemas/GetChannelStreamScheduleResponse/Data/Vacation +class ResponseVacation extends TwitchData: + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation starts. + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation ends. + @export var end_time: String: + set(val): + end_time = val + track_data(&"end_time", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_start_time: String, _end_time: String) -> ResponseVacation: + var response_vacation: ResponseVacation = ResponseVacation.new() + response_vacation.start_time = _start_time + response_vacation.end_time = _end_time + return response_vacation + + + static func from_json(d: Dictionary) -> ResponseVacation: + var result: ResponseVacation = ResponseVacation.new() + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("end_time", null) != null: + result.end_time = d["end_time"] + return result + + + +## The information used to page through a list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). +## #/components/schemas/GetChannelStreamScheduleResponse/Data/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ query parameter to this value. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_channel_stream_schedule +## #/components/schemas/GetChannelStreamScheduleOpt +class Opt extends TwitchData: + + ## The ID of the scheduled segment to return. To specify more than one segment, include the ID of each segment you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 100 IDs. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The UTC date and time that identifies when in the broadcaster’s schedule to start returning segments. If not specified, the request returns segments starting after the current UTC date and time. Specify the date and time in RFC3339 format (for example, `2022-09-01T00:00:00Z`). + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## Not supported. + @export var utc_offset: String: + set(val): + utc_offset = val + track_data(&"utc_offset", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 25 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("utc_offset", null) != null: + result.utc_offset = d["utc_offset"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd.uid b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd.uid new file mode 100644 index 00000000..431a4c1a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd.uid @@ -0,0 +1 @@ +uid://de3xoglwtwjlr diff --git a/addons/twitcher/generated/twitch_get_channel_teams.gd b/addons/twitcher/generated/twitch_get_channel_teams.gd new file mode 100644 index 00000000..3a1c016c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_teams.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChannelTeams + + + +## +## #/components/schemas/GetChannelTeamsResponse +class Response extends TwitchData: + + ## The list of teams that the broadcaster is a member of. Returns an empty array if the broadcaster is not a member of a team. + @export var data: Array[TwitchChannelTeam]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChannelTeam]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChannelTeam.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_channel_teams.gd.uid b/addons/twitcher/generated/twitch_get_channel_teams.gd.uid new file mode 100644 index 00000000..4f8bf58a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_channel_teams.gd.uid @@ -0,0 +1 @@ +uid://b17l78dhk563g diff --git a/addons/twitcher/generated/twitch_get_charity_campaign.gd b/addons/twitcher/generated/twitch_get_charity_campaign.gd new file mode 100644 index 00000000..0e500327 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_charity_campaign.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCharityCampaign + + + +## +## #/components/schemas/GetCharityCampaignResponse +class Response extends TwitchData: + + ## A list that contains the charity campaign that the broadcaster is currently running. The list is empty if the broadcaster is not running a charity campaign; the campaign information is not available after the campaign ends. + @export var data: Array[TwitchCharityCampaign]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCharityCampaign]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCharityCampaign.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_charity_campaign.gd.uid b/addons/twitcher/generated/twitch_get_charity_campaign.gd.uid new file mode 100644 index 00000000..5f83f668 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_charity_campaign.gd.uid @@ -0,0 +1 @@ +uid://drkf2tjmuenau diff --git a/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd new file mode 100644 index 00000000..eb374a1c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCharityCampaignDonations + + + +## +## #/components/schemas/GetCharityCampaignDonationsResponse +class Response extends TwitchData: + + ## A list that contains the donations that users have made to the broadcaster’s charity campaign. The list is empty if the broadcaster is not currently running a charity campaign; the donation information is not available after the campaign ends. + @export var data: Array[TwitchCharityCampaignDonation]: + set(val): + data = val + track_data(&"data", val) + + ## An object that contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCharityCampaignDonation]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCharityCampaignDonation.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## An object that contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetCharityCampaignDonationsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_charity_campaign_donations +## #/components/schemas/GetCharityCampaignDonationsOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd.uid b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd.uid new file mode 100644 index 00000000..e2baa376 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd.uid @@ -0,0 +1 @@ +uid://drtw3qt21v18r diff --git a/addons/twitcher/generated/twitch_get_chat_settings.gd b/addons/twitcher/generated/twitch_get_chat_settings.gd new file mode 100644 index 00000000..12520b6b --- /dev/null +++ b/addons/twitcher/generated/twitch_get_chat_settings.gd @@ -0,0 +1,65 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChatSettings + + + +## +## #/components/schemas/GetChatSettingsResponse +class Response extends TwitchData: + + ## The list of chat settings. The list contains a single object with all the settings. + @export var data: Array[TwitchChatSettings]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChatSettings]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChatSettings.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_chat_settings +## #/components/schemas/GetChatSettingsOpt +class Opt extends TwitchData: + + ## The ID of the broadcaster or one of the broadcaster’s moderators. + ## + ## This field is required only if you want to include the `non_moderator_chat_delay` and `non_moderator_chat_delay_duration` settings in the response. + ## + ## If you specify this field, this ID must match the user ID in the user access token. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_chat_settings.gd.uid b/addons/twitcher/generated/twitch_get_chat_settings.gd.uid new file mode 100644 index 00000000..40c8799c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_chat_settings.gd.uid @@ -0,0 +1 @@ +uid://1tlajx7p4nrs diff --git a/addons/twitcher/generated/twitch_get_chatters.gd b/addons/twitcher/generated/twitch_get_chatters.gd new file mode 100644 index 00000000..c17b94a3 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_chatters.gd @@ -0,0 +1,154 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetChatters + + + +## +## #/components/schemas/GetChattersResponse +class Response extends TwitchData: + + ## The list of users that are connected to the broadcaster’s chat room. The list is empty if no users are connected to the chat room. + @export var data: Array[TwitchChatter]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + + ## The total number of users that are connected to the broadcaster’s chat room. As you page through the list, the number of users may change as users join and leave the chat room. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChatter], _total: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.total = _total + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChatter.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + if d.get("total", null) != null: + result.total = d["total"] + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + total = response.total + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetChattersResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_chatters +## #/components/schemas/GetChattersOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 1,000\. The default is 100. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_chatters.gd.uid b/addons/twitcher/generated/twitch_get_chatters.gd.uid new file mode 100644 index 00000000..049d1372 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_chatters.gd.uid @@ -0,0 +1 @@ +uid://cpdam4aowa666 diff --git a/addons/twitcher/generated/twitch_get_cheermotes.gd b/addons/twitcher/generated/twitch_get_cheermotes.gd new file mode 100644 index 00000000..80ede2bb --- /dev/null +++ b/addons/twitcher/generated/twitch_get_cheermotes.gd @@ -0,0 +1,63 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCheermotes + + + +## +## #/components/schemas/GetCheermotesResponse +class Response extends TwitchData: + + ## The list of Cheermotes. The list is in ascending order by the `order` field’s value. + @export var data: Array[TwitchCheermote]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCheermote]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCheermote.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_cheermotes +## #/components/schemas/GetCheermotesOpt +class Opt extends TwitchData: + + ## The ID of the broadcaster whose custom Cheermotes you want to get. Specify the broadcaster’s ID if you want to include the broadcaster’s Cheermotes in the response (not all broadcasters upload Cheermotes). If not specified, the response contains only global Cheermotes. + ## + ## If the broadcaster uploaded Cheermotes, the `type` field in the response is set to **channel\_custom**. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_cheermotes.gd.uid b/addons/twitcher/generated/twitch_get_cheermotes.gd.uid new file mode 100644 index 00000000..f884db2f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_cheermotes.gd.uid @@ -0,0 +1 @@ +uid://ntigonlydvam diff --git a/addons/twitcher/generated/twitch_get_clips.gd b/addons/twitcher/generated/twitch_get_clips.gd new file mode 100644 index 00000000..71cf1397 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_clips.gd @@ -0,0 +1,201 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetClips + + + +## +## #/components/schemas/GetClipsResponse +class Response extends TwitchData: + + ## The list of video clips. For clips returned by _game\_id_ or _broadcaster\_id_, the list is in descending order by view count. For lists returned by _id_, the list is in the same order as the input IDs. + @export var data: Array[TwitchClip]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchClip]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchClip.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetClipsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ or _before_ query parameter to this value depending on whether you’re paging forwards or backwards. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_clips +## #/components/schemas/GetClipsOpt +class Opt extends TwitchData: + + ## An ID that identifies the broadcaster whose video clips you want to get. Use this parameter to get clips that were captured from the broadcaster’s streams. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## An ID that identifies the game whose clips you want to get. Use this parameter to get clips that were captured from streams that were playing this game. + @export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + + ## An ID that identifies the clip to get. To specify more than one ID, include this parameter for each clip you want to get. For example, `id=foo&id=bar`. You may specify a maximum of 100 IDs. The API ignores duplicate IDs and IDs that aren’t found. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The start date used to filter clips. The API returns only clips within the start and end date window. Specify the date and time in RFC3339 format. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The end date used to filter clips. If not specified, the time window is the start date plus one week. Specify the date and time in RFC3339 format. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + ## The maximum number of clips to return per page in the response. The minimum page size is 1 clip per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## A Boolean value that determines whether the response includes featured clips. If **true**, returns only clips that are featured. If **false**, returns only clips that aren’t featured. All clips are returned if this parameter is not present. + @export var is_featured: bool: + set(val): + is_featured = val + track_data(&"is_featured", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("before", null) != null: + result.before = d["before"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("is_featured", null) != null: + result.is_featured = d["is_featured"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_clips.gd.uid b/addons/twitcher/generated/twitch_get_clips.gd.uid new file mode 100644 index 00000000..95949a20 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_clips.gd.uid @@ -0,0 +1 @@ +uid://3ouaj7trgxli diff --git a/addons/twitcher/generated/twitch_get_conduit_shards.gd b/addons/twitcher/generated/twitch_get_conduit_shards.gd new file mode 100644 index 00000000..8f381359 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_conduit_shards.gd @@ -0,0 +1,263 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetConduitShards + + + +## +## #/components/schemas/GetConduitShardsResponse +class Response extends TwitchData: + + ## List of information about a conduit's shards. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## Contains information used to page through a list of results. The object is empty if there are no more pages left to page through. + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## List of information about a conduit's shards. +## #/components/schemas/GetConduitShardsResponse/Data +class ResponseData extends TwitchData: + + ## Shard ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The shard status. The subscriber receives events only for enabled shards. Possible values are: + ## + ## * enabled — The shard is enabled. + ## * webhook\_callback\_verification\_pending — The shard is pending verification of the specified callback URL. + ## * webhook\_callback\_verification\_failed — The specified callback URL failed verification. + ## * notification\_failures\_exceeded — The notification delivery failure rate was too high. + ## * websocket\_disconnected — The client closed the connection. + ## * websocket\_failed\_ping\_pong — The client failed to respond to a ping message. + ## * websocket\_received\_inbound\_traffic — The client sent a non-pong message. Clients may only send pong messages (and only in response to a ping message). + ## * websocket\_internal\_error — The Twitch WebSocket server experienced an unexpected error. + ## * websocket\_network\_timeout — The Twitch WebSocket server timed out writing the message to the client. + ## * websocket\_network\_error — The Twitch WebSocket server experienced a network error writing the message to the client. + ## * websocket\_failed\_to\_reconnect - The client failed to reconnect to the Twitch WebSocket server within the required time after a Reconnect Message. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## The transport details used to send the notifications. + @export var transport: ResponseTransport: + set(val): + transport = val + track_data(&"transport", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _status: String, _transport: ResponseTransport) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.status = _status + response_data.transport = _transport + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("transport", null) != null: + result.transport = ResponseTransport.from_json(d["transport"]) + return result + + + +## The transport details used to send the notifications. +## #/components/schemas/GetConduitShardsResponse/Data/Transport +class ResponseTransport extends TwitchData: + + ## The transport method. Possible values are: + ## + ## * webhook + ## * websocket + @export var method: String: + set(val): + method = val + track_data(&"method", val) + + ## The callback URL where the notifications are sent. Included only if method is set to webhook. + @export var callback: String: + set(val): + callback = val + track_data(&"callback", val) + + ## An ID that identifies the WebSocket that notifications are sent to. Included only if method is set to websocket. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + ## The UTC date and time that the WebSocket connection was established. Included only if method is set to websocket. + @export var connected_at: String: + set(val): + connected_at = val + track_data(&"connected_at", val) + + ## The UTC date and time that the WebSocket connection was lost. Included only if method is set to websocket. + @export var disconnected_at: String: + set(val): + disconnected_at = val + track_data(&"disconnected_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_method: String) -> ResponseTransport: + var response_transport: ResponseTransport = ResponseTransport.new() + response_transport.method = _method + return response_transport + + + static func from_json(d: Dictionary) -> ResponseTransport: + var result: ResponseTransport = ResponseTransport.new() + if d.get("method", null) != null: + result.method = d["method"] + if d.get("callback", null) != null: + result.callback = d["callback"] + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + if d.get("connected_at", null) != null: + result.connected_at = d["connected_at"] + if d.get("disconnected_at", null) != null: + result.disconnected_at = d["disconnected_at"] + return result + + + +## Contains information used to page through a list of results. The object is empty if there are no more pages left to page through. +## #/components/schemas/GetConduitShardsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_conduit_shards +## #/components/schemas/GetConduitShardsOpt +class Opt extends TwitchData: + + ## Status to filter by. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## The cursor used to get the next page of results. The pagination object in the response contains the cursor’s value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("status", null) != null: + result.status = d["status"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_conduit_shards.gd.uid b/addons/twitcher/generated/twitch_get_conduit_shards.gd.uid new file mode 100644 index 00000000..efe60100 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_conduit_shards.gd.uid @@ -0,0 +1 @@ +uid://dsdt7dyevwgt0 diff --git a/addons/twitcher/generated/twitch_get_conduits.gd b/addons/twitcher/generated/twitch_get_conduits.gd new file mode 100644 index 00000000..84dc5db3 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_conduits.gd @@ -0,0 +1,71 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetConduits + + + +## +## #/components/schemas/GetConduitsResponse +class Response extends TwitchData: + + ## List of information about the client’s conduits. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## List of information about the client’s conduits. +## #/components/schemas/GetConduitsResponse/Data +class ResponseData extends TwitchData: + + ## Conduit ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## Number of shards associated with this conduit. + @export var shard_count: int: + set(val): + shard_count = val + track_data(&"shard_count", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _shard_count: int) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.shard_count = _shard_count + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("shard_count", null) != null: + result.shard_count = d["shard_count"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_conduits.gd.uid b/addons/twitcher/generated/twitch_get_conduits.gd.uid new file mode 100644 index 00000000..888d4fd0 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_conduits.gd.uid @@ -0,0 +1 @@ +uid://yp50wtnjicn5 diff --git a/addons/twitcher/generated/twitch_get_content_classification_labels.gd b/addons/twitcher/generated/twitch_get_content_classification_labels.gd new file mode 100644 index 00000000..6fe96c9c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_content_classification_labels.gd @@ -0,0 +1,62 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetContentClassificationLabels + + + +## +## #/components/schemas/GetContentClassificationLabelsResponse +class Response extends TwitchData: + + ## A list that contains information about the available content classification labels. + @export var data: Array[TwitchContentClassificationLabel]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchContentClassificationLabel]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchContentClassificationLabel.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_content_classification_labels +## #/components/schemas/GetContentClassificationLabelsOpt +class Opt extends TwitchData: + + ## Locale for the Content Classification Labels. You may specify a maximum of 1 locale. Default: `“en-US”` + ## Supported locales: `"bg-BG", "cs-CZ", "da-DK", "da-DK", "de-DE", "el-GR", "en-GB", "en-US", "es-ES", "es-MX", "fi-FI", "fr-FR", "hu-HU", "it-IT", "ja-JP", "ko-KR", "nl-NL", "no-NO", "pl-PL", "pt-BT", "pt-PT", "ro-RO", "ru-RU", "sk-SK", "sv-SE", "th-TH", "tr-TR", "vi-VN", "zh-CN", "zh-TW"` + @export var locale: String: + set(val): + locale = val + track_data(&"locale", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("locale", null) != null: + result.locale = d["locale"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_content_classification_labels.gd.uid b/addons/twitcher/generated/twitch_get_content_classification_labels.gd.uid new file mode 100644 index 00000000..d3fea5cc --- /dev/null +++ b/addons/twitcher/generated/twitch_get_content_classification_labels.gd.uid @@ -0,0 +1 @@ +uid://4dbbek007cw5 diff --git a/addons/twitcher/generated/twitch_get_creator_goals.gd b/addons/twitcher/generated/twitch_get_creator_goals.gd new file mode 100644 index 00000000..ed320b49 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_creator_goals.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCreatorGoals + + + +## +## #/components/schemas/GetCreatorGoalsResponse +class Response extends TwitchData: + + ## The list of goals. The list is empty if the broadcaster hasn’t created goals. + @export var data: Array[TwitchCreatorGoal]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCreatorGoal]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCreatorGoal.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_creator_goals.gd.uid b/addons/twitcher/generated/twitch_get_creator_goals.gd.uid new file mode 100644 index 00000000..5b8acdac --- /dev/null +++ b/addons/twitcher/generated/twitch_get_creator_goals.gd.uid @@ -0,0 +1 @@ +uid://cdthsrrek3ohf diff --git a/addons/twitcher/generated/twitch_get_custom_reward.gd b/addons/twitcher/generated/twitch_get_custom_reward.gd new file mode 100644 index 00000000..67fffd95 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_custom_reward.gd @@ -0,0 +1,72 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCustomReward + + + +## +## #/components/schemas/GetCustomRewardResponse +class Response extends TwitchData: + + ## A list of custom rewards. The list is in ascending order by `id`. If the broadcaster hasn’t created custom rewards, the list is empty. + @export var data: Array[TwitchCustomReward]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCustomReward]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCustomReward.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_custom_reward +## #/components/schemas/GetCustomRewardOpt +class Opt extends TwitchData: + + ## A list of IDs to filter the rewards by. To specify more than one ID, include this parameter for each reward you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 50 IDs. + ## + ## Duplicate IDs are ignored. The response contains only the IDs that were found. If none of the IDs were found, the response is 404 Not Found. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## A Boolean value that determines whether the response contains only the custom rewards that the app may manage (the app is identified by the ID in the Client-Id header). Set to **true** to get only the custom rewards that the app may manage. The default is **false**. + @export var only_manageable_rewards: bool: + set(val): + only_manageable_rewards = val + track_data(&"only_manageable_rewards", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("only_manageable_rewards", null) != null: + result.only_manageable_rewards = d["only_manageable_rewards"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_custom_reward.gd.uid b/addons/twitcher/generated/twitch_get_custom_reward.gd.uid new file mode 100644 index 00000000..36cc0466 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_custom_reward.gd.uid @@ -0,0 +1 @@ +uid://vevdxmqy34h2 diff --git a/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd new file mode 100644 index 00000000..1ea99e37 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd @@ -0,0 +1,184 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetCustomRewardRedemption + + + +## +## #/components/schemas/GetCustomRewardRedemptionResponse +class Response extends TwitchData: + + ## The list of redemptions for the specified reward. The list is empty if there are no redemptions that match the redemption criteria. + @export var data: Array[TwitchCustomRewardRedemption]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCustomRewardRedemption]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCustomRewardRedemption.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetCustomRewardRedemptionResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_custom_reward_redemption +## #/components/schemas/GetCustomRewardRedemptionOpt +class Opt extends TwitchData: + + ## The status of the redemptions to return. The possible case-sensitive values are: + ## + ## * CANCELED + ## * FULFILLED + ## * UNFULFILLED + ## + ## **NOTE**: This field is required only if you don’t specify the _id_ query parameter. + ## + ## **NOTE**: Canceled and fulfilled redemptions are returned for only a few days after they’re canceled or fulfilled. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## A list of IDs to filter the redemptions by. To specify more than one ID, include this parameter for each redemption you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 50 IDs. + ## + ## Duplicate IDs are ignored. The response contains only the IDs that were found. If none of the IDs were found, the response is 404 Not Found. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The order to sort redemptions by. The possible case-sensitive values are: + ## + ## * OLDEST + ## * NEWEST + ## + ## The default is OLDEST. + @export var sort: String: + set(val): + sort = val + track_data(&"sort", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read more](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The maximum number of redemptions to return per page in the response. The minimum page size is 1 redemption per page and the maximum is 50\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("status", null) != null: + result.status = d["status"] + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("sort", null) != null: + result.sort = d["sort"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("first", null) != null: + result.first = d["first"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd.uid b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd.uid new file mode 100644 index 00000000..359c0f70 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd.uid @@ -0,0 +1 @@ +uid://miy23m4ho6ea diff --git a/addons/twitcher/generated/twitch_get_drops_entitlements.gd b/addons/twitcher/generated/twitch_get_drops_entitlements.gd new file mode 100644 index 00000000..bf57c894 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_drops_entitlements.gd @@ -0,0 +1,180 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetDropsEntitlements + + + +## +## #/components/schemas/GetDropsEntitlementsResponse +class Response extends TwitchData: + + ## The list of entitlements. + @export var data: Array[TwitchDropsEntitlement]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchDropsEntitlement]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchDropsEntitlement.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetDropsEntitlementsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ query parameter to this value to page forward through the results. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_drops_entitlements +## #/components/schemas/GetDropsEntitlementsOpt +class Opt extends TwitchData: + + ## An ID that identifies the entitlement to get. Include this parameter for each entitlement you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 100 IDs. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## An ID that identifies a user that was granted entitlements. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## An ID that identifies a game that offered entitlements. + @export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + + ## The entitlement’s fulfillment status. Used to filter the list to only those with the specified status. Possible values are: + ## + ## * CLAIMED + ## * FULFILLED + @export var fulfillment_status: String: + set(val): + fulfillment_status = val + track_data(&"fulfillment_status", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The maximum number of entitlements to return per page in the response. The minimum page size is 1 entitlement per page and the maximum is 1000\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("fulfillment_status", null) != null: + result.fulfillment_status = d["fulfillment_status"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("first", null) != null: + result.first = d["first"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_drops_entitlements.gd.uid b/addons/twitcher/generated/twitch_get_drops_entitlements.gd.uid new file mode 100644 index 00000000..bfd0e9a4 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_drops_entitlements.gd.uid @@ -0,0 +1 @@ +uid://dns35jr4ihknl diff --git a/addons/twitcher/generated/twitch_get_emote_sets.gd b/addons/twitcher/generated/twitch_get_emote_sets.gd new file mode 100644 index 00000000..d9b8eb51 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_emote_sets.gd @@ -0,0 +1,44 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetEmoteSets + + + +## +## #/components/schemas/GetEmoteSetsResponse +class Response extends TwitchData: + + ## The list of emotes found in the specified emote sets. The list is empty if none of the IDs were found. The list is in the same order as the set IDs specified in the request. Each set contains one or more emoticons. + @export var data: Array[TwitchEmote]: + set(val): + data = val + track_data(&"data", val) + + ## A templated URL. Use the values from the `id`, `format`, `scale`, and `theme_mode` fields to replace the like-named placeholder strings in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. For information about what the template looks like and how to use it to fetch emotes, see [Emote CDN URL format](https://dev.twitch.tv/docs/irc/emotes#cdn-template). You should use this template instead of using the URLs in the `images` object. + @export var template: String: + set(val): + template = val + track_data(&"template", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchEmote], _template: String) -> Response: + var response: Response = Response.new() + response.data = _data + response.template = _template + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchEmote.from_json(value)) + if d.get("template", null) != null: + result.template = d["template"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_emote_sets.gd.uid b/addons/twitcher/generated/twitch_get_emote_sets.gd.uid new file mode 100644 index 00000000..180e1ed9 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_emote_sets.gd.uid @@ -0,0 +1 @@ +uid://yg1njcnm6m7a diff --git a/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd new file mode 100644 index 00000000..5a6846ea --- /dev/null +++ b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd @@ -0,0 +1,140 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetEventSubSubscriptions + + + +## +## #/components/schemas/GetEventSubSubscriptionsResponse +class Response extends TwitchData: + + ## The list of subscriptions. The list is ordered by the oldest subscription first. The list is empty if the client hasn't created subscriptions or there are no subscriptions that match the specified filter criteria. + @export var data: Array[TwitchEventSubSubscription]: + set(val): + data = val + track_data(&"data", val) + + ## The total number of subscriptions that you've created. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The sum of all of your subscription costs. [Learn More](https://dev.twitch.tv/docs/eventsub/manage-subscriptions/#subscription-limits) + @export var total_cost: int: + set(val): + total_cost = val + track_data(&"total_cost", val) + + ## The maximum total cost that you're allowed to incur for all subscriptions that you create. + @export var max_total_cost: int: + set(val): + max_total_cost = val + track_data(&"max_total_cost", val) + + ## An object that contains the cursor used to get the next page of subscriptions. The object is empty if there are no more pages to get. The number of subscriptions returned per page is undertermined. + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchEventSubSubscription], _total: int, _total_cost: int, _max_total_cost: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.total = _total + response.total_cost = _total_cost + response.max_total_cost = _max_total_cost + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchEventSubSubscription.from_json(value)) + if d.get("total", null) != null: + result.total = d["total"] + if d.get("total_cost", null) != null: + result.total_cost = d["total_cost"] + if d.get("max_total_cost", null) != null: + result.max_total_cost = d["max_total_cost"] + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + total = response.total + total_cost = response.total_cost + max_total_cost = response.max_total_cost + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## An object that contains the cursor used to get the next page of subscriptions. The object is empty if there are no more pages to get. The number of subscriptions returned per page is undertermined. +## #/components/schemas/GetEventSubSubscriptionsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor value that you set the _after_ query parameter to. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd.uid b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd.uid new file mode 100644 index 00000000..e3d0d5f2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd.uid @@ -0,0 +1 @@ +uid://bqgn1u2ftu8nl diff --git a/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd b/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd new file mode 100644 index 00000000..401f5cb2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd @@ -0,0 +1,84 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetEventsubSubscriptions + + + +## All optional parameters for TwitchAPI.get_eventsub_subscriptions +## #/components/schemas/GetEventsubSubscriptionsOpt +class Opt extends TwitchData: + + ## Filter subscriptions by its status. Possible values are: + ## + ## * enabled — The subscription is enabled. + ## * webhook\_callback\_verification\_pending — The subscription is pending verification of the specified callback URL. + ## * webhook\_callback\_verification\_failed — The specified callback URL failed verification. + ## * notification\_failures\_exceeded — The notification delivery failure rate was too high. + ## * authorization\_revoked — The authorization was revoked for one or more users specified in the **Condition** object. + ## * moderator\_removed — The moderator that authorized the subscription is no longer one of the broadcaster's moderators. + ## * user\_removed — One of the users specified in the **Condition** object was removed. + ## * chat\_user\_banned - The user specified in the **Condition** object was banned from the broadcaster's chat. + ## * version\_removed — The subscription to subscription type and version is no longer supported. + ## * beta\_maintenance — The subscription to the beta subscription type was removed due to maintenance. + ## * websocket\_disconnected — The client closed the connection. + ## * websocket\_failed\_ping\_pong — The client failed to respond to a ping message. + ## * websocket\_received\_inbound\_traffic — The client sent a non-pong message. Clients may only send pong messages (and only in response to a ping message). + ## * websocket\_connection\_unused — The client failed to subscribe to events within the required time. + ## * websocket\_internal\_error — The Twitch WebSocket server experienced an unexpected error. + ## * websocket\_network\_timeout — The Twitch WebSocket server timed out writing the message to the client. + ## * websocket\_network\_error — The Twitch WebSocket server experienced a network error writing the message to the client. + ## * websocket\_failed\_to\_reconnect - The client failed to reconnect to the Twitch WebSocket server within the required time after a Reconnect Message. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## Filter subscriptions by subscription type. For a list of subscription types, see [Subscription Types](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types#subscription-types). + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## Filter subscriptions by user ID. The response contains subscriptions where this ID matches a user ID that you specified in the **Condition** object when you [created the subscription](https://dev.twitch.tv/docs/api/reference#create-eventsub-subscription). + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## Returns an array with the subscription matching the ID (as long as it is owned by the client making the request), or an empty array if there is no matching subscription. + @export var subscription_id: String: + set(val): + subscription_id = val + track_data(&"subscription_id", val) + + ## The cursor used to get the next page of results. The `pagination` object in the response contains the cursor's value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("status", null) != null: + result.status = d["status"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("subscription_id", null) != null: + result.subscription_id = d["subscription_id"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd.uid b/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd.uid new file mode 100644 index 00000000..1fb134ae --- /dev/null +++ b/addons/twitcher/generated/twitch_get_eventsub_subscriptions.gd.uid @@ -0,0 +1 @@ +uid://b2n2k0rn7ldkg diff --git a/addons/twitcher/generated/twitch_get_extension_analytics.gd b/addons/twitcher/generated/twitch_get_extension_analytics.gd new file mode 100644 index 00000000..9bbe40c2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_analytics.gd @@ -0,0 +1,188 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionAnalytics + + + +## +## #/components/schemas/GetExtensionAnalyticsResponse +class Response extends TwitchData: + + ## A list of reports. The reports are returned in no particular order; however, the data within each report is in ascending order by date (newest first). The report contains one row of data per day of the reporting window; the report contains rows for only those days that the extension was used. The array is empty if there are no reports. + @export var data: Array[TwitchExtensionAnalytics]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionAnalytics]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionAnalytics.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetExtensionAnalyticsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_extension_analytics +## #/components/schemas/GetExtensionAnalyticsOpt +class Opt extends TwitchData: + + ## The extension's client ID. If specified, the response contains a report for the specified extension. If not specified, the response includes a report for each extension that the authenticated user owns. + @export var extension_id: String: + set(val): + extension_id = val + track_data(&"extension_id", val) + + ## The type of analytics report to get. Possible values are: + ## + ## * overview\_v2 + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The reporting window's start date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z). + ## + ## The start date must be on or after January 31, 2018\. If you specify an earlier date, the API ignores it and uses January 31, 2018\. If you specify a start date, you must specify an end date. If you don't specify a start and end date, the report includes all available data since January 31, 2018. + ## + ## The report contains one row of data for each day in the reporting window. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The reporting window's end date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-27T00:00:00Z). The report is inclusive of the end date. + ## + ## Specify an end date only if you provide a start date. Because it can take up to two days for the data to be available, you must specify an end date that's earlier than today minus one to two days. If not, the API ignores your end date and uses an end date that is today minus one to two days. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + ## The maximum number of report URLs to return per page in the response. The minimum page size is 1 URL per page and the maximum is 100 URLs per page. The default is 20. + ## + ## **NOTE**: While you may specify a maximum value of 100, the response will contain at most 20 URLs per page. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + ## + ## This parameter is ignored if the _extension\_id_ parameter is set. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("extension_id", null) != null: + result.extension_id = d["extension_id"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_analytics.gd.uid b/addons/twitcher/generated/twitch_get_extension_analytics.gd.uid new file mode 100644 index 00000000..c16edced --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_analytics.gd.uid @@ -0,0 +1 @@ +uid://cnls1i1hbmgvl diff --git a/addons/twitcher/generated/twitch_get_extension_bits_products.gd b/addons/twitcher/generated/twitch_get_extension_bits_products.gd new file mode 100644 index 00000000..c132f11f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_bits_products.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionBitsProducts + + + +## +## #/components/schemas/GetExtensionBitsProductsResponse +class Response extends TwitchData: + + ## A list of Bits products that the extension created. The list is in ascending SKU order. The list is empty if the extension hasn’t created any products or they’re all expired or disabled. + @export var data: Array[TwitchExtensionBitsProduct]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionBitsProduct]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionBitsProduct.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_extension_bits_products +## #/components/schemas/GetExtensionBitsProductsOpt +class Opt extends TwitchData: + + ## A Boolean value that determines whether to include disabled or expired Bits products in the response. The default is **false**. + @export var should_include_all: bool: + set(val): + should_include_all = val + track_data(&"should_include_all", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("should_include_all", null) != null: + result.should_include_all = d["should_include_all"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_bits_products.gd.uid b/addons/twitcher/generated/twitch_get_extension_bits_products.gd.uid new file mode 100644 index 00000000..1b26a749 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_bits_products.gd.uid @@ -0,0 +1 @@ +uid://7vblsvayv6ak diff --git a/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd b/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd new file mode 100644 index 00000000..d82b85b5 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionConfigurationSegment + + + +## +## #/components/schemas/GetExtensionConfigurationSegmentResponse +class Response extends TwitchData: + + ## The list of requested configuration segments. The list is returned in the same order that you specified the list of segments in the request. + @export var data: Array[TwitchExtensionConfigurationSegment]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionConfigurationSegment]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionConfigurationSegment.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_extension_configuration_segment +## #/components/schemas/GetExtensionConfigurationSegmentOpt +class Opt extends TwitchData: + + ## The ID of the broadcaster that installed the extension. This parameter is required if you set the _segment_ parameter to broadcaster or developer. Do not specify this parameter if you set _segment_ to global. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd.uid b/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd.uid new file mode 100644 index 00000000..fc5e4cc0 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_configuration_segment.gd.uid @@ -0,0 +1 @@ +uid://c04yrkdk5tnas diff --git a/addons/twitcher/generated/twitch_get_extension_live_channels.gd b/addons/twitcher/generated/twitch_get_extension_live_channels.gd new file mode 100644 index 00000000..f9c157c3 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_live_channels.gd @@ -0,0 +1,117 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionLiveChannels + + + +## +## #/components/schemas/GetExtensionLiveChannelsResponse +class Response extends TwitchData: + + ## The list of broadcasters that are streaming live and that have installed or activated the extension. + @export var data: Array[TwitchExtensionLiveChannel]: + set(val): + data = val + track_data(&"data", val) + + ## This field contains the cursor used to page through the results. The field is empty if there are no more pages left to page through. Note that this field is a string compared to other endpoints that use a **Pagination** object. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: String: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionLiveChannel]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionLiveChannel.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = d["pagination"] + return result + + + + func _has_pagination() -> bool: + if pagination == null || pagination == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## All optional parameters for TwitchAPI.get_extension_live_channels +## #/components/schemas/GetExtensionLiveChannelsOpt +class Opt extends TwitchData: + + ## The specific maximum number of items per page in the response. The actual number returned may be less than this limit. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The `pagination` field in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_live_channels.gd.uid b/addons/twitcher/generated/twitch_get_extension_live_channels.gd.uid new file mode 100644 index 00000000..1f0f593b --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_live_channels.gd.uid @@ -0,0 +1 @@ +uid://b6q2p6ypyf5ie diff --git a/addons/twitcher/generated/twitch_get_extension_secrets.gd b/addons/twitcher/generated/twitch_get_extension_secrets.gd new file mode 100644 index 00000000..64fcefbb --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_secrets.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionSecrets + + + +## +## #/components/schemas/GetExtensionSecretsResponse +class Response extends TwitchData: + + ## The list of shared secrets that the extension created. + @export var data: Array[TwitchExtensionSecret]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionSecret]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionSecret.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_secrets.gd.uid b/addons/twitcher/generated/twitch_get_extension_secrets.gd.uid new file mode 100644 index 00000000..7143d215 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_secrets.gd.uid @@ -0,0 +1 @@ +uid://by4byfrvnnwlj diff --git a/addons/twitcher/generated/twitch_get_extension_transactions.gd b/addons/twitcher/generated/twitch_get_extension_transactions.gd new file mode 100644 index 00000000..fa779031 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_transactions.gd @@ -0,0 +1,153 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensionTransactions + + + +## +## #/components/schemas/GetExtensionTransactionsResponse +class Response extends TwitchData: + + ## The list of transactions. + @export var data: Array[TwitchExtensionTransaction]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionTransaction]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionTransaction.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetExtensionTransactionsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_extension_transactions +## #/components/schemas/GetExtensionTransactionsOpt +class Opt extends TwitchData: + + ## A transaction ID used to filter the list of transactions. Specify this parameter for each transaction you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 100 IDs. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extension_transactions.gd.uid b/addons/twitcher/generated/twitch_get_extension_transactions.gd.uid new file mode 100644 index 00000000..06d5174c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extension_transactions.gd.uid @@ -0,0 +1 @@ +uid://dk1rvby8ko4b5 diff --git a/addons/twitcher/generated/twitch_get_extensions.gd b/addons/twitcher/generated/twitch_get_extensions.gd new file mode 100644 index 00000000..e81125a4 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extensions.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetExtensions + + + +## +## #/components/schemas/GetExtensionsResponse +class Response extends TwitchData: + + ## A list that contains the specified extension. + @export var data: Array[TwitchExtension]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtension]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtension.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_extensions +## #/components/schemas/GetExtensionsOpt +class Opt extends TwitchData: + + ## The version of the extension to get. If not specified, it returns the latest, released version. If you don’t have a released version, you must specify a version; otherwise, the list is empty. + @export var extension_version: String: + set(val): + extension_version = val + track_data(&"extension_version", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("extension_version", null) != null: + result.extension_version = d["extension_version"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_extensions.gd.uid b/addons/twitcher/generated/twitch_get_extensions.gd.uid new file mode 100644 index 00000000..e7b594ce --- /dev/null +++ b/addons/twitcher/generated/twitch_get_extensions.gd.uid @@ -0,0 +1 @@ +uid://ve5t7kmi7hbr diff --git a/addons/twitcher/generated/twitch_get_followed_channels.gd b/addons/twitcher/generated/twitch_get_followed_channels.gd new file mode 100644 index 00000000..80454bff --- /dev/null +++ b/addons/twitcher/generated/twitch_get_followed_channels.gd @@ -0,0 +1,216 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetFollowedChannels + + + +## +## #/components/schemas/GetFollowedChannelsResponse +class Response extends TwitchData: + + ## The list of broadcasters that the user follows. The list is in descending order by `followed_at` (with the most recently followed broadcaster first). The list is empty if the user doesn’t follow anyone. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + + ## The total number of broadcasters that the user follows. As someone pages through the list, the number may change as the user follows or unfollows broadcasters. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData], _total: int) -> Response: + var response: Response = Response.new() + response.data = _data + response.total = _total + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + if d.get("total", null) != null: + result.total = d["total"] + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + total = response.total + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The list of broadcasters that the user follows. The list is in descending order by `followed_at` (with the most recently followed broadcaster first). The list is empty if the user doesn’t follow anyone. +## #/components/schemas/GetFollowedChannelsResponse/Data +class ResponseData extends TwitchData: + + ## An ID that uniquely identifies the broadcaster that this user is following. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The broadcaster’s display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## The UTC timestamp when the user started following the broadcaster. + @export var followed_at: String: + set(val): + followed_at = val + track_data(&"followed_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _followed_at: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_login = _broadcaster_login + response_data.broadcaster_name = _broadcaster_name + response_data.followed_at = _followed_at + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("followed_at", null) != null: + result.followed_at = d["followed_at"] + return result + + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). +## #/components/schemas/GetFollowedChannelsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_followed_channels +## #/components/schemas/GetFollowedChannelsOpt +class Opt extends TwitchData: + + ## A broadcaster’s ID. Use this parameter to see whether the user follows this broadcaster. If specified, the response contains this broadcaster if the user follows them. If not specified, the response contains all broadcasters that the user follows. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read more](https://dev.twitch.tv/docs/api/guide#pagination). + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_followed_channels.gd.uid b/addons/twitcher/generated/twitch_get_followed_channels.gd.uid new file mode 100644 index 00000000..17a53fd2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_followed_channels.gd.uid @@ -0,0 +1 @@ +uid://d0jkb77gcevai diff --git a/addons/twitcher/generated/twitch_get_followed_streams.gd b/addons/twitcher/generated/twitch_get_followed_streams.gd new file mode 100644 index 00000000..4fd53f26 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_followed_streams.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetFollowedStreams + + + +## +## #/components/schemas/GetFollowedStreamsResponse +class Response extends TwitchData: + + ## The list of live streams of broadcasters that the specified user follows. The list is in descending order by the number of viewers watching the stream. Because viewers come and go during a stream, it’s possible to find duplicate or missing streams in the list as you page through the results. The list is empty if none of the followed broadcasters are streaming live. + @export var data: Array[TwitchStream]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStream]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStream.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetFollowedStreamsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ query parameter to this value. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_followed_streams +## #/components/schemas/GetFollowedStreamsOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 100. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_followed_streams.gd.uid b/addons/twitcher/generated/twitch_get_followed_streams.gd.uid new file mode 100644 index 00000000..83735789 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_followed_streams.gd.uid @@ -0,0 +1 @@ +uid://dcebcynwot4r5 diff --git a/addons/twitcher/generated/twitch_get_game_analytics.gd b/addons/twitcher/generated/twitch_get_game_analytics.gd new file mode 100644 index 00000000..9f6e5f88 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_game_analytics.gd @@ -0,0 +1,188 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGameAnalytics + + + +## +## #/components/schemas/GetGameAnalyticsResponse +class Response extends TwitchData: + + ## A list of reports. The reports are returned in no particular order; however, the data within each report is in ascending order by date (newest first). The report contains one row of data per day of the reporting window; the report contains rows for only those days that the game was used. A report is available only if the game was broadcast for at least 5 hours over the reporting period. The array is empty if there are no reports. + @export var data: Array[TwitchGameAnalytics]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGameAnalytics]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGameAnalytics.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetGameAnalyticsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_game_analytics +## #/components/schemas/GetGameAnalyticsOpt +class Opt extends TwitchData: + + ## The game’s client ID. If specified, the response contains a report for the specified game. If not specified, the response includes a report for each of the authenticated user’s games. + @export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + + ## The type of analytics report to get. Possible values are: + ## + ## * overview\_v2 + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The reporting window’s start date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z). If you specify a start date, you must specify an end date. + ## + ## The start date must be within one year of today’s date. If you specify an earlier date, the API ignores it and uses a date that’s one year prior to today’s date. If you don’t specify a start and end date, the report includes all available data for the last 365 days from today. + ## + ## The report contains one row of data for each day in the reporting window. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The reporting window’s end date, in RFC3339 format. Set the time portion to zeroes (for example, 2021-10-22T00:00:00Z). The report is inclusive of the end date. + ## + ## Specify an end date only if you provide a start date. Because it can take up to two days for the data to be available, you must specify an end date that’s earlier than today minus one to two days. If not, the API ignores your end date and uses an end date that is today minus one to two days. + @export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + ## The maximum number of report URLs to return per page in the response. The minimum page size is 1 URL per page and the maximum is 100 URLs per page. The default is 20. + ## + ## **NOTE**: While you may specify a maximum value of 100, the response will contain at most 20 URLs per page. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + ## + ## This parameter is ignored if _game\_id_ parameter is set. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_game_analytics.gd.uid b/addons/twitcher/generated/twitch_get_game_analytics.gd.uid new file mode 100644 index 00000000..062161f1 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_game_analytics.gd.uid @@ -0,0 +1 @@ +uid://do4py5hysupck diff --git a/addons/twitcher/generated/twitch_get_games.gd b/addons/twitcher/generated/twitch_get_games.gd new file mode 100644 index 00000000..3cbd13fb --- /dev/null +++ b/addons/twitcher/generated/twitch_get_games.gd @@ -0,0 +1,80 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGames + + + +## +## #/components/schemas/GetGamesResponse +class Response extends TwitchData: + + ## The list of categories and games. The list is empty if the specified categories and games weren’t found. + @export var data: Array[TwitchGame]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGame]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGame.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_games +## #/components/schemas/GetGamesOpt +class Opt extends TwitchData: + + ## The ID of the category or game to get. Include this parameter for each category or game you want to get. For example, `&id=1234&id=5678`. You may specify a maximum of 100 IDs. The endpoint ignores duplicate and invalid IDs or IDs that weren’t found. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The name of the category or game to get. The name must exactly match the category’s or game’s title. Include this parameter for each category or game you want to get. For example, `&name=foo&name=bar`. You may specify a maximum of 100 names. The endpoint ignores duplicate names and names that weren’t found. + @export var name: Array[String]: + set(val): + name = val + track_data(&"name", val) + + ## The [IGDB](https://www.igdb.com/) ID of the game to get. Include this parameter for each game you want to get. For example, `&igdb_id=1234&igdb_id=5678`. You may specify a maximum of 100 IDs. The endpoint ignores duplicate and invalid IDs or IDs that weren’t found. + @export var igdb_id: Array[String]: + set(val): + igdb_id = val + track_data(&"igdb_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("name", null) != null: + for value in d["name"]: + result.name.append(value) + if d.get("igdb_id", null) != null: + for value in d["igdb_id"]: + result.igdb_id.append(value) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_games.gd.uid b/addons/twitcher/generated/twitch_get_games.gd.uid new file mode 100644 index 00000000..9d7310f9 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_games.gd.uid @@ -0,0 +1 @@ +uid://cjog83t263bu diff --git a/addons/twitcher/generated/twitch_get_global_chat_badges.gd b/addons/twitcher/generated/twitch_get_global_chat_badges.gd new file mode 100644 index 00000000..de9c9b91 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_global_chat_badges.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGlobalChatBadges + + + +## +## #/components/schemas/GetGlobalChatBadgesResponse +class Response extends TwitchData: + + ## The list of chat badges. The list is sorted in ascending order by `set_id`, and within a set, the list is sorted in ascending order by `id`. + @export var data: Array[TwitchChatBadge]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChatBadge]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChatBadge.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_global_chat_badges.gd.uid b/addons/twitcher/generated/twitch_get_global_chat_badges.gd.uid new file mode 100644 index 00000000..fdf5ed38 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_global_chat_badges.gd.uid @@ -0,0 +1 @@ +uid://crdb6dfnmxmy5 diff --git a/addons/twitcher/generated/twitch_get_global_emotes.gd b/addons/twitcher/generated/twitch_get_global_emotes.gd new file mode 100644 index 00000000..967b8c19 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_global_emotes.gd @@ -0,0 +1,44 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGlobalEmotes + + + +## +## #/components/schemas/GetGlobalEmotesResponse +class Response extends TwitchData: + + ## The list of global emotes. + @export var data: Array[TwitchGlobalEmote]: + set(val): + data = val + track_data(&"data", val) + + ## A templated URL. Use the values from the `id`, `format`, `scale`, and `theme_mode` fields to replace the like-named placeholder strings in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. For information about what the template looks like and how to use it to fetch emotes, see [Emote CDN URL format](https://dev.twitch.tv/docs/irc/emotes#cdn-template). You should use this template instead of using the URLs in the `images` object. + @export var template: String: + set(val): + template = val + track_data(&"template", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGlobalEmote], _template: String) -> Response: + var response: Response = Response.new() + response.data = _data + response.template = _template + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGlobalEmote.from_json(value)) + if d.get("template", null) != null: + result.template = d["template"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_global_emotes.gd.uid b/addons/twitcher/generated/twitch_get_global_emotes.gd.uid new file mode 100644 index 00000000..70543e8d --- /dev/null +++ b/addons/twitcher/generated/twitch_get_global_emotes.gd.uid @@ -0,0 +1 @@ +uid://d2o3tmbyvdtc6 diff --git a/addons/twitcher/generated/twitch_get_guest_star_invites.gd b/addons/twitcher/generated/twitch_get_guest_star_invites.gd new file mode 100644 index 00000000..47dc1800 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_guest_star_invites.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGuestStarInvites + + + +## +## #/components/schemas/GetGuestStarInvitesResponse +class Response extends TwitchData: + + ## A list of invite objects describing the invited user as well as their ready status. + @export var data: Array[TwitchGuestStarInvite]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGuestStarInvite]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGuestStarInvite.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_guest_star_invites.gd.uid b/addons/twitcher/generated/twitch_get_guest_star_invites.gd.uid new file mode 100644 index 00000000..f6d00b40 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_guest_star_invites.gd.uid @@ -0,0 +1 @@ +uid://cm54sr3e5jfh5 diff --git a/addons/twitcher/generated/twitch_get_guest_star_session.gd b/addons/twitcher/generated/twitch_get_guest_star_session.gd new file mode 100644 index 00000000..6cd64455 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_guest_star_session.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetGuestStarSession + + + +## +## #/components/schemas/GetGuestStarSessionResponse +class Response extends TwitchData: + + ## Summary of the session details + @export var data: Array[TwitchGuestStarSession]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGuestStarSession]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGuestStarSession.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_guest_star_session.gd.uid b/addons/twitcher/generated/twitch_get_guest_star_session.gd.uid new file mode 100644 index 00000000..65b3314c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_guest_star_session.gd.uid @@ -0,0 +1 @@ +uid://bbd4c101q84dg diff --git a/addons/twitcher/generated/twitch_get_hype_train_events.gd b/addons/twitcher/generated/twitch_get_hype_train_events.gd new file mode 100644 index 00000000..78aa217f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_hype_train_events.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetHypeTrainEvents + + + +## +## #/components/schemas/GetHypeTrainEventsResponse +class Response extends TwitchData: + + ## The list of Hype Train events. The list is empty if the broadcaster hasn’t run a Hype Train within the last 5 days. + @export var data: Array[TwitchHypeTrainEvent]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchHypeTrainEvent]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchHypeTrainEvent.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetHypeTrainEventsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_hype_train_events +## #/components/schemas/GetHypeTrainEventsOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 1. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_hype_train_events.gd.uid b/addons/twitcher/generated/twitch_get_hype_train_events.gd.uid new file mode 100644 index 00000000..c2e27ca4 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_hype_train_events.gd.uid @@ -0,0 +1 @@ +uid://cgm5e2hbk2elm diff --git a/addons/twitcher/generated/twitch_get_hype_train_status.gd b/addons/twitcher/generated/twitch_get_hype_train_status.gd new file mode 100644 index 00000000..e419d275 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_hype_train_status.gd @@ -0,0 +1,421 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetHypeTrainStatus + + + +## +## #/components/schemas/GetHypeTrainStatusResponse +class Response extends TwitchData: + + ## A list that contains information related to the channel’s Hype Train. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## An object with information about the channel’s Hype Train records. Null if a Hype Train has not occurred. + @export var all_time_high: ResponseAllTimeHigh: + set(val): + all_time_high = val + track_data(&"all_time_high", val) + + ## An object with information about the channel’s shared Hype Train records. Null if a Hype Train has not occurred. + @export var shared_all_time_high: ResponseSharedAllTimeHigh: + set(val): + shared_all_time_high = val + track_data(&"shared_all_time_high", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData], _all_time_high: ResponseAllTimeHigh, _shared_all_time_high: ResponseSharedAllTimeHigh) -> Response: + var response: Response = Response.new() + response.data = _data + response.all_time_high = _all_time_high + response.shared_all_time_high = _shared_all_time_high + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("all_time_high", null) != null: + result.all_time_high = ResponseAllTimeHigh.from_json(d["all_time_high"]) + if d.get("shared_all_time_high", null) != null: + result.shared_all_time_high = ResponseSharedAllTimeHigh.from_json(d["shared_all_time_high"]) + return result + + + +## A list that contains information related to the channel’s Hype Train. +## #/components/schemas/GetHypeTrainStatusResponse/Data +class ResponseData extends TwitchData: + + ## An object describing the current Hype Train. Null if a Hype Train is not active. + @export var current: ResponseCurrent: + set(val): + current = val + track_data(&"current", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_current: ResponseCurrent) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.current = _current + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("current", null) != null: + result.current = ResponseCurrent.from_json(d["current"]) + return result + + + +## An object describing the current Hype Train. Null if a Hype Train is not active. +## #/components/schemas/GetHypeTrainStatusResponse/Data/Current +class ResponseCurrent extends TwitchData: + + ## The Hype Train ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The broadcaster ID. + @export var broadcaster_user_id: String: + set(val): + broadcaster_user_id = val + track_data(&"broadcaster_user_id", val) + + ## The broadcaster login. + @export var broadcaster_user_login: String: + set(val): + broadcaster_user_login = val + track_data(&"broadcaster_user_login", val) + + ## The broadcaster display name. + @export var broadcaster_user_name: String: + set(val): + broadcaster_user_name = val + track_data(&"broadcaster_user_name", val) + + ## The current level of the Hype Train. + @export var level: int: + set(val): + level = val + track_data(&"level", val) + + ## Total points contributed to the Hype Train. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The number of points contributed to the Hype Train at the current level. + @export var progress: int: + set(val): + progress = val + track_data(&"progress", val) + + ## The number of points required to reach the next level. + @export var goal: int: + set(val): + goal = val + track_data(&"goal", val) + + ## The contributors with the most points contributed. + @export var top_contributions: Array[ResponseTopContributions]: + set(val): + top_contributions = val + track_data(&"top_contributions", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _broadcaster_user_id: String, _broadcaster_user_login: String, _broadcaster_user_name: String, _level: int, _total: int, _progress: int, _goal: int, _top_contributions: Array[ResponseTopContributions]) -> ResponseCurrent: + var response_current: ResponseCurrent = ResponseCurrent.new() + response_current.id = _id + response_current.broadcaster_user_id = _broadcaster_user_id + response_current.broadcaster_user_login = _broadcaster_user_login + response_current.broadcaster_user_name = _broadcaster_user_name + response_current.level = _level + response_current.total = _total + response_current.progress = _progress + response_current.goal = _goal + response_current.top_contributions = _top_contributions + return response_current + + + static func from_json(d: Dictionary) -> ResponseCurrent: + var result: ResponseCurrent = ResponseCurrent.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_user_id", null) != null: + result.broadcaster_user_id = d["broadcaster_user_id"] + if d.get("broadcaster_user_login", null) != null: + result.broadcaster_user_login = d["broadcaster_user_login"] + if d.get("broadcaster_user_name", null) != null: + result.broadcaster_user_name = d["broadcaster_user_name"] + if d.get("level", null) != null: + result.level = d["level"] + if d.get("total", null) != null: + result.total = d["total"] + if d.get("progress", null) != null: + result.progress = d["progress"] + if d.get("goal", null) != null: + result.goal = d["goal"] + if d.get("top_contributions", null) != null: + for value in d["top_contributions"]: + result.top_contributions.append(ResponseTopContributions.from_json(value)) + return result + + + +## The contributors with the most points contributed. +## #/components/schemas/GetHypeTrainStatusResponse/Data/Current/TopContributions +class ResponseTopContributions extends TwitchData: + + ## The ID of the user that made the contribution. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The user’s login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The user’s display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + ## The type of the Hype Train. Possible values are: + ## + ## * **treasure** + ## * **golden\_kappa** + ## * **regular** + ## + ## [Learn More](https://help.twitch.tv/s/article/hype-train-guide#special) + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The total number of points contributed for the type. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## A list containing the broadcasters participating in the shared Hype Train. Null if the Hype Train is not shared. + @export var shared_train_participants: Array[ResponseSharedTrainParticipants]: + set(val): + shared_train_participants = val + track_data(&"shared_train_participants", val) + + ## The time when the Hype Train started. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The time when the Hype Train expires. The expiration is extended when the Hype Train reaches a new level. + @export var expires_at: String: + set(val): + expires_at = val + track_data(&"expires_at", val) + + ## Indicates if the Hype Train is shared. When true, shared\_train\_participants will contain the list of broadcasters the train is shared with. + @export var is_shared_train: bool: + set(val): + is_shared_train = val + track_data(&"is_shared_train", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_user_id: String, _user_login: String, _user_name: String, _type: String, _total: int, _shared_train_participants: Array[ResponseSharedTrainParticipants], _started_at: String, _expires_at: String, _is_shared_train: bool) -> ResponseTopContributions: + var response_top_contributions: ResponseTopContributions = ResponseTopContributions.new() + response_top_contributions.user_id = _user_id + response_top_contributions.user_login = _user_login + response_top_contributions.user_name = _user_name + response_top_contributions.type = _type + response_top_contributions.total = _total + response_top_contributions.shared_train_participants = _shared_train_participants + response_top_contributions.started_at = _started_at + response_top_contributions.expires_at = _expires_at + response_top_contributions.is_shared_train = _is_shared_train + return response_top_contributions + + + static func from_json(d: Dictionary) -> ResponseTopContributions: + var result: ResponseTopContributions = ResponseTopContributions.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("total", null) != null: + result.total = d["total"] + if d.get("shared_train_participants", null) != null: + for value in d["shared_train_participants"]: + result.shared_train_participants.append(ResponseSharedTrainParticipants.from_json(value)) + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("expires_at", null) != null: + result.expires_at = d["expires_at"] + if d.get("is_shared_train", null) != null: + result.is_shared_train = d["is_shared_train"] + return result + + + +## A list containing the broadcasters participating in the shared Hype Train. Null if the Hype Train is not shared. +## #/components/schemas/GetHypeTrainStatusResponse/Data/Current/TopContributions/SharedTrainParticipants +class ResponseSharedTrainParticipants extends TwitchData: + + ## The broadcaster ID. + @export var broadcaster_user_id: String: + set(val): + broadcaster_user_id = val + track_data(&"broadcaster_user_id", val) + + ## The broadcaster login. + @export var broadcaster_user_login: String: + set(val): + broadcaster_user_login = val + track_data(&"broadcaster_user_login", val) + + ## The broadcaster display name. + @export var broadcaster_user_name: String: + set(val): + broadcaster_user_name = val + track_data(&"broadcaster_user_name", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_user_id: String, _broadcaster_user_login: String, _broadcaster_user_name: String) -> ResponseSharedTrainParticipants: + var response_shared_train_participants: ResponseSharedTrainParticipants = ResponseSharedTrainParticipants.new() + response_shared_train_participants.broadcaster_user_id = _broadcaster_user_id + response_shared_train_participants.broadcaster_user_login = _broadcaster_user_login + response_shared_train_participants.broadcaster_user_name = _broadcaster_user_name + return response_shared_train_participants + + + static func from_json(d: Dictionary) -> ResponseSharedTrainParticipants: + var result: ResponseSharedTrainParticipants = ResponseSharedTrainParticipants.new() + if d.get("broadcaster_user_id", null) != null: + result.broadcaster_user_id = d["broadcaster_user_id"] + if d.get("broadcaster_user_login", null) != null: + result.broadcaster_user_login = d["broadcaster_user_login"] + if d.get("broadcaster_user_name", null) != null: + result.broadcaster_user_name = d["broadcaster_user_name"] + return result + + + +## An object with information about the channel’s Hype Train records. Null if a Hype Train has not occurred. +## #/components/schemas/GetHypeTrainStatusResponse/AllTimeHigh +class ResponseAllTimeHigh extends TwitchData: + + ## The level of the record Hype Train. + @export var level: int: + set(val): + level = val + track_data(&"level", val) + + ## Total points contributed to the record Hype Train. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The time when the record was achieved. + @export var achieved_at: String: + set(val): + achieved_at = val + track_data(&"achieved_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_level: int, _total: int, _achieved_at: String) -> ResponseAllTimeHigh: + var response_all_time_high: ResponseAllTimeHigh = ResponseAllTimeHigh.new() + response_all_time_high.level = _level + response_all_time_high.total = _total + response_all_time_high.achieved_at = _achieved_at + return response_all_time_high + + + static func from_json(d: Dictionary) -> ResponseAllTimeHigh: + var result: ResponseAllTimeHigh = ResponseAllTimeHigh.new() + if d.get("level", null) != null: + result.level = d["level"] + if d.get("total", null) != null: + result.total = d["total"] + if d.get("achieved_at", null) != null: + result.achieved_at = d["achieved_at"] + return result + + + +## An object with information about the channel’s shared Hype Train records. Null if a Hype Train has not occurred. +## #/components/schemas/GetHypeTrainStatusResponse/SharedAllTimeHigh +class ResponseSharedAllTimeHigh extends TwitchData: + + ## The level of the record Hype Train. + @export var level: int: + set(val): + level = val + track_data(&"level", val) + + ## Total points contributed to the record Hype Train. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The time when the record was achieved. + @export var achieved_at: String: + set(val): + achieved_at = val + track_data(&"achieved_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_level: int, _total: int, _achieved_at: String) -> ResponseSharedAllTimeHigh: + var response_shared_all_time_high: ResponseSharedAllTimeHigh = ResponseSharedAllTimeHigh.new() + response_shared_all_time_high.level = _level + response_shared_all_time_high.total = _total + response_shared_all_time_high.achieved_at = _achieved_at + return response_shared_all_time_high + + + static func from_json(d: Dictionary) -> ResponseSharedAllTimeHigh: + var result: ResponseSharedAllTimeHigh = ResponseSharedAllTimeHigh.new() + if d.get("level", null) != null: + result.level = d["level"] + if d.get("total", null) != null: + result.total = d["total"] + if d.get("achieved_at", null) != null: + result.achieved_at = d["achieved_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_hype_train_status.gd.uid b/addons/twitcher/generated/twitch_get_hype_train_status.gd.uid new file mode 100644 index 00000000..b3be8ffb --- /dev/null +++ b/addons/twitcher/generated/twitch_get_hype_train_status.gd.uid @@ -0,0 +1 @@ +uid://ctsrm7gynti4u diff --git a/addons/twitcher/generated/twitch_get_moderated_channels.gd b/addons/twitcher/generated/twitch_get_moderated_channels.gd new file mode 100644 index 00000000..2df0517f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_moderated_channels.gd @@ -0,0 +1,191 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetModeratedChannels + + + +## +## #/components/schemas/GetModeratedChannelsResponse +class Response extends TwitchData: + + ## The list of channels that the user has moderator privileges in. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The list of channels that the user has moderator privileges in. +## #/components/schemas/GetModeratedChannelsResponse/Data +class ResponseData extends TwitchData: + + ## An ID that uniquely identifies the channel this user can moderate. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The channel’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The channels’ display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_login = _broadcaster_login + response_data.broadcaster_name = _broadcaster_name + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + return result + + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. +## #/components/schemas/GetModeratedChannelsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_moderated_channels +## #/components/schemas/GetModeratedChannelsOpt +class Opt extends TwitchData: + + ## The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The maximum number of items to return per page in the response. + ## + ## Minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("after", null) != null: + result.after = d["after"] + if d.get("first", null) != null: + result.first = d["first"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_moderated_channels.gd.uid b/addons/twitcher/generated/twitch_get_moderated_channels.gd.uid new file mode 100644 index 00000000..6d50557a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_moderated_channels.gd.uid @@ -0,0 +1 @@ +uid://ducqwy81pu67u diff --git a/addons/twitcher/generated/twitch_get_moderators.gd b/addons/twitcher/generated/twitch_get_moderators.gd new file mode 100644 index 00000000..be05dc0f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_moderators.gd @@ -0,0 +1,155 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetModerators + + + +## +## #/components/schemas/GetModeratorsResponse +class Response extends TwitchData: + + ## The list of moderators. + @export var data: Array[TwitchUserModerator]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserModerator]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserModerator.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetModeratorsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_moderators +## #/components/schemas/GetModeratorsOpt +class Opt extends TwitchData: + + ## A list of user IDs used to filter the results. To specify more than one ID, include this parameter for each moderator you want to get. For example, `user_id=1234&user_id=5678`. You may specify a maximum of 100 IDs. + ## + ## The returned list includes only the users from the list who are moderators in the broadcaster’s channel. The list is returned in the same order as you specified the IDs. + @export var user_id: Array[String]: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + for value in d["user_id"]: + result.user_id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_moderators.gd.uid b/addons/twitcher/generated/twitch_get_moderators.gd.uid new file mode 100644 index 00000000..7af2a12c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_moderators.gd.uid @@ -0,0 +1 @@ +uid://cihnhjydprxy5 diff --git a/addons/twitcher/generated/twitch_get_polls.gd b/addons/twitcher/generated/twitch_get_polls.gd new file mode 100644 index 00000000..73b7a4ba --- /dev/null +++ b/addons/twitcher/generated/twitch_get_polls.gd @@ -0,0 +1,155 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetPolls + + + +## +## #/components/schemas/GetPollsResponse +class Response extends TwitchData: + + ## A list of polls. The polls are returned in descending order of start time unless you specify IDs in the request, in which case they're returned in the same order as you passed them in the request. The list is empty if the broadcaster hasn't created polls. + @export var data: Array[TwitchPoll]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPoll]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPoll.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetPollsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request's _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_polls +## #/components/schemas/GetPollsOpt +class Opt extends TwitchData: + + ## A list of IDs that identify the polls to return. To specify more than one ID, include this parameter for each poll you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 20 IDs. + ## + ## Specify this parameter only if you want to filter the list that the request returns. The endpoint ignores duplicate IDs and those not owned by this broadcaster. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 20 items per page. The default is 20. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_polls.gd.uid b/addons/twitcher/generated/twitch_get_polls.gd.uid new file mode 100644 index 00000000..ed707fe5 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_polls.gd.uid @@ -0,0 +1 @@ +uid://c7lc5fg1ep2b0 diff --git a/addons/twitcher/generated/twitch_get_predictions.gd b/addons/twitcher/generated/twitch_get_predictions.gd new file mode 100644 index 00000000..60db8c03 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_predictions.gd @@ -0,0 +1,153 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetPredictions + + + +## +## #/components/schemas/GetPredictionsResponse +class Response extends TwitchData: + + ## The broadcaster’s list of Channel Points Predictions. The list is sorted in descending ordered by when the prediction began (the most recent prediction is first). The list is empty if the broadcaster hasn’t created predictions. + @export var data: Array[TwitchPrediction]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchPrediction]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchPrediction.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetPredictionsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_predictions +## #/components/schemas/GetPredictionsOpt +class Opt extends TwitchData: + + ## The ID of the prediction to get. To specify more than one ID, include this parameter for each prediction you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 25 IDs. The endpoint ignores duplicate IDs and those not owned by the broadcaster. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 25 items per page. The default is 20. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_predictions.gd.uid b/addons/twitcher/generated/twitch_get_predictions.gd.uid new file mode 100644 index 00000000..7bcaff6b --- /dev/null +++ b/addons/twitcher/generated/twitch_get_predictions.gd.uid @@ -0,0 +1 @@ +uid://cenvkii2xgne diff --git a/addons/twitcher/generated/twitch_get_released_extensions.gd b/addons/twitcher/generated/twitch_get_released_extensions.gd new file mode 100644 index 00000000..e685475f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_released_extensions.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetReleasedExtensions + + + +## +## #/components/schemas/GetReleasedExtensionsResponse +class Response extends TwitchData: + + ## A list that contains the specified extension. + @export var data: Array[TwitchExtension]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtension]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtension.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_released_extensions +## #/components/schemas/GetReleasedExtensionsOpt +class Opt extends TwitchData: + + ## The version of the extension to get. If not specified, it returns the latest version. + @export var extension_version: String: + set(val): + extension_version = val + track_data(&"extension_version", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("extension_version", null) != null: + result.extension_version = d["extension_version"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_released_extensions.gd.uid b/addons/twitcher/generated/twitch_get_released_extensions.gd.uid new file mode 100644 index 00000000..e7011b02 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_released_extensions.gd.uid @@ -0,0 +1 @@ +uid://cptbi53x52e8i diff --git a/addons/twitcher/generated/twitch_get_shared_chat_session.gd b/addons/twitcher/generated/twitch_get_shared_chat_session.gd new file mode 100644 index 00000000..fb9e3d06 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_shared_chat_session.gd @@ -0,0 +1,126 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetSharedChatSession + + + +## +## #/components/schemas/GetSharedChatSessionResponse +class Response extends TwitchData: + + ## + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## +## #/components/schemas/GetSharedChatSessionResponse/Data +class ResponseData extends TwitchData: + + ## The unique identifier for the shared chat session. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + ## The User ID of the host channel. + @export var host_broadcaster_id: String: + set(val): + host_broadcaster_id = val + track_data(&"host_broadcaster_id", val) + + ## The list of participants in the session. + @export var participants: Array[ResponseParticipants]: + set(val): + participants = val + track_data(&"participants", val) + + ## The UTC date and time (in RFC3339 format) for when the session was created. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## The UTC date and time (in RFC3339 format) for when the session was last updated. + @export var updated_at: String: + set(val): + updated_at = val + track_data(&"updated_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_session_id: String, _host_broadcaster_id: String, _participants: Array[ResponseParticipants], _created_at: String, _updated_at: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.session_id = _session_id + response_data.host_broadcaster_id = _host_broadcaster_id + response_data.participants = _participants + response_data.created_at = _created_at + response_data.updated_at = _updated_at + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + if d.get("host_broadcaster_id", null) != null: + result.host_broadcaster_id = d["host_broadcaster_id"] + if d.get("participants", null) != null: + for value in d["participants"]: + result.participants.append(ResponseParticipants.from_json(value)) + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("updated_at", null) != null: + result.updated_at = d["updated_at"] + return result + + + +## The list of participants in the session. +## #/components/schemas/GetSharedChatSessionResponse/Data/Participants +class ResponseParticipants extends TwitchData: + + ## The User ID of the participant channel. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String) -> ResponseParticipants: + var response_participants: ResponseParticipants = ResponseParticipants.new() + response_participants.broadcaster_id = _broadcaster_id + return response_participants + + + static func from_json(d: Dictionary) -> ResponseParticipants: + var result: ResponseParticipants = ResponseParticipants.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_shared_chat_session.gd.uid b/addons/twitcher/generated/twitch_get_shared_chat_session.gd.uid new file mode 100644 index 00000000..4ab324d8 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_shared_chat_session.gd.uid @@ -0,0 +1 @@ +uid://8b63h8eiw85h diff --git a/addons/twitcher/generated/twitch_get_shield_mode_status.gd b/addons/twitcher/generated/twitch_get_shield_mode_status.gd new file mode 100644 index 00000000..d5a070fa --- /dev/null +++ b/addons/twitcher/generated/twitch_get_shield_mode_status.gd @@ -0,0 +1,98 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetShieldModeStatus + + + +## +## #/components/schemas/GetShieldModeStatusResponse +class Response extends TwitchData: + + ## A list that contains a single object with the broadcaster’s Shield Mode status. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains a single object with the broadcaster’s Shield Mode status. +## #/components/schemas/GetShieldModeStatusResponse/Data +class ResponseData extends TwitchData: + + ## A Boolean value that determines whether Shield Mode is active. Is **true** if the broadcaster activated Shield Mode; otherwise, **false**. + @export var is_active: bool: + set(val): + is_active = val + track_data(&"is_active", val) + + ## An ID that identifies the moderator that last activated Shield Mode. Is an empty string if Shield Mode hasn’t been previously activated. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The moderator’s login name. Is an empty string if Shield Mode hasn’t been previously activated. + @export var moderator_login: String: + set(val): + moderator_login = val + track_data(&"moderator_login", val) + + ## The moderator’s display name. Is an empty string if Shield Mode hasn’t been previously activated. + @export var moderator_name: String: + set(val): + moderator_name = val + track_data(&"moderator_name", val) + + ## The UTC timestamp (in RFC3339 format) of when Shield Mode was last activated. Is an empty string if Shield Mode hasn’t been previously activated. + @export var last_activated_at: String: + set(val): + last_activated_at = val + track_data(&"last_activated_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_is_active: bool, _moderator_id: String, _moderator_login: String, _moderator_name: String, _last_activated_at: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.is_active = _is_active + response_data.moderator_id = _moderator_id + response_data.moderator_login = _moderator_login + response_data.moderator_name = _moderator_name + response_data.last_activated_at = _last_activated_at + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("is_active", null) != null: + result.is_active = d["is_active"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("moderator_login", null) != null: + result.moderator_login = d["moderator_login"] + if d.get("moderator_name", null) != null: + result.moderator_name = d["moderator_name"] + if d.get("last_activated_at", null) != null: + result.last_activated_at = d["last_activated_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_shield_mode_status.gd.uid b/addons/twitcher/generated/twitch_get_shield_mode_status.gd.uid new file mode 100644 index 00000000..12cddac9 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_shield_mode_status.gd.uid @@ -0,0 +1 @@ +uid://chhd8mjs2nctc diff --git a/addons/twitcher/generated/twitch_get_stream_key.gd b/addons/twitcher/generated/twitch_get_stream_key.gd new file mode 100644 index 00000000..d9be5cd2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_key.gd @@ -0,0 +1,62 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetStreamKey + + + +## +## #/components/schemas/GetStreamKeyResponse +class Response extends TwitchData: + + ## A list that contains the channel’s stream key. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains the channel’s stream key. +## #/components/schemas/GetStreamKeyResponse/Data +class ResponseData extends TwitchData: + + ## The channel’s stream key. + @export var stream_key: String: + set(val): + stream_key = val + track_data(&"stream_key", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_stream_key: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.stream_key = _stream_key + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("stream_key", null) != null: + result.stream_key = d["stream_key"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_stream_key.gd.uid b/addons/twitcher/generated/twitch_get_stream_key.gd.uid new file mode 100644 index 00000000..530aeb85 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_key.gd.uid @@ -0,0 +1 @@ +uid://dd8dg0c0g51pm diff --git a/addons/twitcher/generated/twitch_get_stream_markers.gd b/addons/twitcher/generated/twitch_get_stream_markers.gd new file mode 100644 index 00000000..409de04f --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_markers.gd @@ -0,0 +1,172 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetStreamMarkers + + + +## +## #/components/schemas/GetStreamMarkersResponse +class Response extends TwitchData: + + ## The list of markers grouped by the user that created the marks. + @export var data: Array[TwitchStreamMarkers]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStreamMarkers]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStreamMarkers.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetStreamMarkersResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ or _before_ query parameter to this value depending on whether you’re paging forwards or backwards. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_stream_markers +## #/components/schemas/GetStreamMarkersOpt +class Opt extends TwitchData: + + ## A user ID. The request returns the markers from this user’s most recent video. This ID must match the user ID in the access token or the user in the access token must be one of the broadcaster’s editors. + ## + ## This parameter and the _video\_id_ query parameter are mutually exclusive. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## A video on demand (VOD)/video ID. The request returns the markers from this VOD/video. The user in the access token must own the video or the user must be one of the broadcaster’s editors. + ## + ## This parameter and the _user\_id_ query parameter are mutually exclusive. + @export var video_id: String: + set(val): + video_id = val + track_data(&"video_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("video_id", null) != null: + result.video_id = d["video_id"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("before", null) != null: + result.before = d["before"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_stream_markers.gd.uid b/addons/twitcher/generated/twitch_get_stream_markers.gd.uid new file mode 100644 index 00000000..bd1a6efa --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_markers.gd.uid @@ -0,0 +1 @@ +uid://c3gr8dg7sft5y diff --git a/addons/twitcher/generated/twitch_get_stream_tags.gd b/addons/twitcher/generated/twitch_get_stream_tags.gd new file mode 100644 index 00000000..1450412c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_tags.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetStreamTags + + + +## +## #/components/schemas/GetStreamTagsResponse +class Response extends TwitchData: + + ## The list of stream tags. The list is empty if the broadcaster or Twitch hasn’t added tags to the broadcaster’s channel. + @export var data: Array[TwitchStreamTag]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStreamTag]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStreamTag.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_stream_tags.gd.uid b/addons/twitcher/generated/twitch_get_stream_tags.gd.uid new file mode 100644 index 00000000..c548aec5 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_stream_tags.gd.uid @@ -0,0 +1 @@ +uid://xmkr128c2xai diff --git a/addons/twitcher/generated/twitch_get_streams.gd b/addons/twitcher/generated/twitch_get_streams.gd new file mode 100644 index 00000000..1a26d3cd --- /dev/null +++ b/addons/twitcher/generated/twitch_get_streams.gd @@ -0,0 +1,203 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetStreams + + + +## +## #/components/schemas/GetStreamsResponse +class Response extends TwitchData: + + ## The list of streams. + @export var data: Array[TwitchStream]: + set(val): + data = val + track_data(&"data", val) + + ## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchStream]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchStream.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## The information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetStreamsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Set the request’s _after_ or _before_ query parameter to this value depending on whether you’re paging forwards or backwards. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_streams +## #/components/schemas/GetStreamsOpt +class Opt extends TwitchData: + + ## A user ID used to filter the list of streams. Returns only the streams of those users that are broadcasting. You may specify a maximum of 100 IDs. To specify multiple IDs, include the _user\_id_ parameter for each user. For example, `&user_id=1234&user_id=5678`. + @export var user_id: Array[String]: + set(val): + user_id = val + track_data(&"user_id", val) + + ## A user login name used to filter the list of streams. Returns only the streams of those users that are broadcasting. You may specify a maximum of 100 login names. To specify multiple names, include the _user\_login_ parameter for each user. For example, `&user_login=foo&user_login=bar`. + @export var user_login: Array[String]: + set(val): + user_login = val + track_data(&"user_login", val) + + ## A game (category) ID used to filter the list of streams. Returns only the streams that are broadcasting the game (category). You may specify a maximum of 100 IDs. To specify multiple IDs, include the _game\_id_ parameter for each game. For example, `&game_id=9876&game_id=5432`. + @export var game_id: Array[String]: + set(val): + game_id = val + track_data(&"game_id", val) + + ## The type of stream to filter the list of streams by. Possible values are: + ## + ## * all + ## * live + ## + ## The default is _all_. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## A language code used to filter the list of streams. Returns only streams that broadcast in the specified language. Specify the language using an ISO 639-1 two-letter language code or _other_ if the broadcast uses a language not in the list of [supported stream languages](https://help.twitch.tv/s/article/languages-on-twitch#streamlang). + ## + ## You may specify a maximum of 100 language codes. To specify multiple languages, include the _language_ parameter for each language. For example, `&language=de&language=fr`. + @export var language: Array[String]: + set(val): + language = val + track_data(&"language", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + for value in d["user_id"]: + result.user_id.append(value) + if d.get("user_login", null) != null: + for value in d["user_login"]: + result.user_login.append(value) + if d.get("game_id", null) != null: + for value in d["game_id"]: + result.game_id.append(value) + if d.get("type", null) != null: + result.type = d["type"] + if d.get("language", null) != null: + for value in d["language"]: + result.language.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("before", null) != null: + result.before = d["before"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_streams.gd.uid b/addons/twitcher/generated/twitch_get_streams.gd.uid new file mode 100644 index 00000000..821ddad2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_streams.gd.uid @@ -0,0 +1 @@ +uid://cuiyg43dqei5d diff --git a/addons/twitcher/generated/twitch_get_teams.gd b/addons/twitcher/generated/twitch_get_teams.gd new file mode 100644 index 00000000..711f827d --- /dev/null +++ b/addons/twitcher/generated/twitch_get_teams.gd @@ -0,0 +1,69 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetTeams + + + +## +## #/components/schemas/GetTeamsResponse +class Response extends TwitchData: + + ## A list that contains the single team that you requested. + @export var data: Array[TwitchTeam]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchTeam]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchTeam.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_teams +## #/components/schemas/GetTeamsOpt +class Opt extends TwitchData: + + ## The name of the team to get. This parameter and the _id_ parameter are mutually exclusive; you must specify the team’s name or ID but not both. + @export var name: String: + set(val): + name = val + track_data(&"name", val) + + ## The ID of the team to get. This parameter and the _name_ parameter are mutually exclusive; you must specify the team’s name or ID but not both. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("name", null) != null: + result.name = d["name"] + if d.get("id", null) != null: + result.id = d["id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_teams.gd.uid b/addons/twitcher/generated/twitch_get_teams.gd.uid new file mode 100644 index 00000000..1487733a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_teams.gd.uid @@ -0,0 +1 @@ +uid://ciyghm6xdkyib diff --git a/addons/twitcher/generated/twitch_get_top_games.gd b/addons/twitcher/generated/twitch_get_top_games.gd new file mode 100644 index 00000000..8f5dbcf4 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_top_games.gd @@ -0,0 +1,152 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetTopGames + + + +## +## #/components/schemas/GetTopGamesResponse +class Response extends TwitchData: + + ## The list of broadcasts. The broadcasts are sorted by the number of viewers, with the most popular first. + @export var data: Array[TwitchGame]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchGame]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchGame.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetTopGamesResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ or _before_ query parameter to get the next or previous page of results. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_top_games +## #/components/schemas/GetTopGamesOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("before", null) != null: + result.before = d["before"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_top_games.gd.uid b/addons/twitcher/generated/twitch_get_top_games.gd.uid new file mode 100644 index 00000000..ede1bb63 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_top_games.gd.uid @@ -0,0 +1 @@ +uid://dcj4fadol01cx diff --git a/addons/twitcher/generated/twitch_get_unban_requests.gd b/addons/twitcher/generated/twitch_get_unban_requests.gd new file mode 100644 index 00000000..3d81eb68 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_unban_requests.gd @@ -0,0 +1,311 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUnbanRequests + + + +## +## #/components/schemas/GetUnbanRequestsResponse +class Response extends TwitchData: + + ## A list that contains information about the channel's unban requests. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## Contains information used to page through a list of results. The object is empty if there are no more pages left to page through. + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## A list that contains information about the channel's unban requests. +## #/components/schemas/GetUnbanRequestsResponse/Data +class ResponseData extends TwitchData: + + ## Unban request ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## User ID of broadcaster whose channel is receiving the unban request. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster's display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## The broadcaster's login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## User ID of moderator who approved/denied the request. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The moderator's login name. + @export var moderator_login: String: + set(val): + moderator_login = val + track_data(&"moderator_login", val) + + ## The moderator's display name. + @export var moderator_name: String: + set(val): + moderator_name = val + track_data(&"moderator_name", val) + + ## User ID of the requestor who is asking for an unban. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The user's login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The user's display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + ## Text of the request from the requesting user. + @export var text: String: + set(val): + text = val + track_data(&"text", val) + + ## Status of the request. One of: + ## + ## * pending + ## * approved + ## * denied + ## * acknowledged + ## * canceled + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## Timestamp of when the unban request was created. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## Timestamp of when moderator/broadcaster approved or denied the request. + @export var resolved_at: String: + set(val): + resolved_at = val + track_data(&"resolved_at", val) + + ## Text input by the resolver (moderator) of the unban. request + @export var resolution_text: String: + set(val): + resolution_text = val + track_data(&"resolution_text", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _moderator_id: String, _moderator_login: String, _moderator_name: String, _user_id: String, _user_login: String, _user_name: String, _text: String, _status: String, _created_at: String, _resolved_at: String, _resolution_text: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_name = _broadcaster_name + response_data.broadcaster_login = _broadcaster_login + response_data.moderator_id = _moderator_id + response_data.moderator_login = _moderator_login + response_data.moderator_name = _moderator_name + response_data.user_id = _user_id + response_data.user_login = _user_login + response_data.user_name = _user_name + response_data.text = _text + response_data.status = _status + response_data.created_at = _created_at + response_data.resolved_at = _resolved_at + response_data.resolution_text = _resolution_text + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("moderator_login", null) != null: + result.moderator_login = d["moderator_login"] + if d.get("moderator_name", null) != null: + result.moderator_name = d["moderator_name"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("text", null) != null: + result.text = d["text"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("resolved_at", null) != null: + result.resolved_at = d["resolved_at"] + if d.get("resolution_text", null) != null: + result.resolution_text = d["resolution_text"] + return result + + + +## Contains information used to page through a list of results. The object is empty if there are no more pages left to page through. +## #/components/schemas/GetUnbanRequestsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_unban_requests +## #/components/schemas/GetUnbanRequestsOpt +class Opt extends TwitchData: + + ## The ID used to filter what unban requests are returned. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## Cursor used to get next page of results. Pagination object in response contains cursor value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The maximum number of items to return per page in response + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("first", null) != null: + result.first = d["first"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_unban_requests.gd.uid b/addons/twitcher/generated/twitch_get_unban_requests.gd.uid new file mode 100644 index 00000000..a539c838 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_unban_requests.gd.uid @@ -0,0 +1 @@ +uid://bdjw8c31hmamj diff --git a/addons/twitcher/generated/twitch_get_user_active_extensions.gd b/addons/twitcher/generated/twitch_get_user_active_extensions.gd new file mode 100644 index 00000000..2a9ada85 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_active_extensions.gd @@ -0,0 +1,103 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUserActiveExtensions + + + +## +## #/components/schemas/GetUserActiveExtensionsResponse +class Response extends TwitchData: + + ## The active extensions that the broadcaster has installed. + @export var data: ResponseData: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> Response: + var response: Response = Response.new() + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + result.data = ResponseData.from_json(d["data"]) + return result + + + +## The active extensions that the broadcaster has installed. +## #/components/schemas/GetUserActiveExtensionsResponse/Data +class ResponseData extends TwitchData: + + ## A dictionary that contains the data for a panel extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the panel’s data for each key. + @export var panel: Dictionary: + set(val): + panel = val + track_data(&"panel", val) + + ## A dictionary that contains the data for a video-overlay extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the overlay’s data for each key. + @export var overlay: Dictionary: + set(val): + overlay = val + track_data(&"overlay", val) + + ## A dictionary that contains the data for a video-component extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the component’s data for each key. + @export var component: Dictionary: + set(val): + component = val + track_data(&"component", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponseData: + var response_data: ResponseData = ResponseData.new() + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("panel", null) != null: + result.panel = d["panel"] + if d.get("overlay", null) != null: + result.overlay = d["overlay"] + if d.get("component", null) != null: + result.component = d["component"] + return result + + + +## All optional parameters for TwitchAPI.get_user_active_extensions +## #/components/schemas/GetUserActiveExtensionsOpt +class Opt extends TwitchData: + + ## The ID of the broadcaster whose active extensions you want to get. + ## + ## This parameter is required if you specify an app access token and is optional if you specify a user access token. If you specify a user access token and don’t specify this parameter, the API uses the user ID from the access token. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_user_active_extensions.gd.uid b/addons/twitcher/generated/twitch_get_user_active_extensions.gd.uid new file mode 100644 index 00000000..f6edf9a2 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_active_extensions.gd.uid @@ -0,0 +1 @@ +uid://behw75yoiwgeu diff --git a/addons/twitcher/generated/twitch_get_user_block_list.gd b/addons/twitcher/generated/twitch_get_user_block_list.gd new file mode 100644 index 00000000..a639251c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_block_list.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUserBlockList + + + +## +## #/components/schemas/GetUserBlockListResponse +class Response extends TwitchData: + + ## The list of blocked users. The list is in descending order by when the user was blocked. + @export var data: Array[TwitchUserBlockList]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserBlockList]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserBlockList.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetUserBlockListResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_user_block_list +## #/components/schemas/GetUserBlockListOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_user_block_list.gd.uid b/addons/twitcher/generated/twitch_get_user_block_list.gd.uid new file mode 100644 index 00000000..0e7b038a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_block_list.gd.uid @@ -0,0 +1 @@ +uid://c5phlcg37egid diff --git a/addons/twitcher/generated/twitch_get_user_chat_color.gd b/addons/twitcher/generated/twitch_get_user_chat_color.gd new file mode 100644 index 00000000..d3ce09c0 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_chat_color.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUserChatColor + + + +## +## #/components/schemas/GetUserChatColorResponse +class Response extends TwitchData: + + ## The list of users and the color code they use for their name. + @export var data: Array[TwitchUserChatColor]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserChatColor]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserChatColor.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_user_chat_color.gd.uid b/addons/twitcher/generated/twitch_get_user_chat_color.gd.uid new file mode 100644 index 00000000..f1293d19 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_chat_color.gd.uid @@ -0,0 +1 @@ +uid://bd8nvrh2prny2 diff --git a/addons/twitcher/generated/twitch_get_user_emotes.gd b/addons/twitcher/generated/twitch_get_user_emotes.gd new file mode 100644 index 00000000..615ba01c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_emotes.gd @@ -0,0 +1,280 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUserEmotes + + + +## +## #/components/schemas/GetUserEmotesResponse +class Response extends TwitchData: + + ## + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## A templated URL. Uses the values from the _id_, _format_, _scale_, and _theme\_mode_ fields to replace the like-named placeholder strings in the templated URL to create a CDN (content delivery network) URL that you use to fetch the emote. + ## + ## For information about what the template looks like and how to use it to fetch emotes, see [Emote CDN URL](https://dev.twitch.tv/docs/irc/emotes#cdn-template) format. + @export var template: String: + set(val): + template = val + track_data(&"template", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. + ## + ## For more information about pagination support, see [Twitch API Guide - Pagination](https://dev.twitch.tv/docs/api/guide#pagination). + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData], _template: String) -> Response: + var response: Response = Response.new() + response.data = _data + response.template = _template + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("template", null) != null: + result.template = d["template"] + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + template = response.template + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## +## #/components/schemas/GetUserEmotesResponse/Data +class ResponseData extends TwitchData: + + ## An ID that uniquely identifies this emote. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The User ID of broadcaster whose channel is receiving the unban request. + @export var name: String: + set(val): + name = val + track_data(&"name", val) + + ## The type of emote. The possible values are: + ## + ## * **none** — No emote type was assigned to this emote. + ## * **bitstier** — A Bits tier emote. + ## * **follower** — A follower emote. + ## * **subscriptions** — A subscriber emote. + ## * **channelpoints** — An emote granted by using channel points. + ## * **rewards** — An emote granted to the user through a special event. + ## * **hypetrain** — An emote granted for participation in a Hype Train. + ## * **prime** — An emote granted for linking an Amazon Prime account. + ## * **turbo** — An emote granted for having Twitch Turbo. + ## * **smilies** — Emoticons supported by Twitch. + ## * **globals** — An emote accessible by everyone. + ## * **owl2019** — Emotes related to Overwatch League 2019. + ## * **twofactor** — Emotes granted by enabling two-factor authentication on an account. + ## * **limitedtime** — Emotes that were granted for only a limited time. + @export var emote_type: String: + set(val): + emote_type = val + track_data(&"emote_type", val) + + ## An ID that identifies the emote set that the emote belongs to. + @export var emote_set_id: String: + set(val): + emote_set_id = val + track_data(&"emote_set_id", val) + + ## The ID of the broadcaster who owns the emote. + @export var owner_id: String: + set(val): + owner_id = val + track_data(&"owner_id", val) + + ## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only static. But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. + ## + ## * **animated** — An animated GIF is available for this emote. + ## * **static** — A static PNG file is available for this emote. + @export var format: Array[String]: + set(val): + format = val + track_data(&"format", val) + + ## The sizes that the emote is available in. For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0\. + ## + ## * **1.0** — A small version (28px x 28px) is available. + ## * **2.0** — A medium version (56px x 56px) is available. + ## * **3.0** — A large version (112px x 112px) is available. + @export var scale: Array[String]: + set(val): + scale = val + track_data(&"scale", val) + + ## The background themes that the emote is available in. + ## + ## * **dark** + ## * **light** + @export var theme_mode: Array[String]: + set(val): + theme_mode = val + track_data(&"theme_mode", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _name: String, _emote_type: String, _emote_set_id: String, _owner_id: String, _format: Array[String], _scale: Array[String], _theme_mode: Array[String]) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.name = _name + response_data.emote_type = _emote_type + response_data.emote_set_id = _emote_set_id + response_data.owner_id = _owner_id + response_data.format = _format + response_data.scale = _scale + response_data.theme_mode = _theme_mode + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("emote_type", null) != null: + result.emote_type = d["emote_type"] + if d.get("emote_set_id", null) != null: + result.emote_set_id = d["emote_set_id"] + if d.get("owner_id", null) != null: + result.owner_id = d["owner_id"] + if d.get("format", null) != null: + for value in d["format"]: + result.format.append(value) + if d.get("scale", null) != null: + for value in d["scale"]: + result.scale.append(value) + if d.get("theme_mode", null) != null: + for value in d["theme_mode"]: + result.theme_mode.append(value) + return result + + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. +## +## For more information about pagination support, see [Twitch API Guide - Pagination](https://dev.twitch.tv/docs/api/guide#pagination). +## #/components/schemas/GetUserEmotesResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s after query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_user_emotes +## #/components/schemas/GetUserEmotesOpt +class Opt extends TwitchData: + + ## The cursor used to get the next page of results. The Pagination object in the response contains the cursor’s value. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The User ID of a broadcaster you wish to get follower emotes of. Using this query parameter will guarantee inclusion of the broadcaster’s follower emotes in the response body. + ## + ## **Note:** If the user specified in `user_id` is subscribed to the broadcaster specified, their follower emotes will appear in the response body regardless if this query parameter is used. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("after", null) != null: + result.after = d["after"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_user_emotes.gd.uid b/addons/twitcher/generated/twitch_get_user_emotes.gd.uid new file mode 100644 index 00000000..f6bc5838 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_emotes.gd.uid @@ -0,0 +1 @@ +uid://cisyrufmqhm05 diff --git a/addons/twitcher/generated/twitch_get_user_extensions.gd b/addons/twitcher/generated/twitch_get_user_extensions.gd new file mode 100644 index 00000000..7c83b3ab --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_extensions.gd @@ -0,0 +1,35 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUserExtensions + + + +## +## #/components/schemas/GetUserExtensionsResponse +class Response extends TwitchData: + + ## The list of extensions that the user has installed. + @export var data: Array[TwitchUserExtension]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserExtension]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserExtension.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_user_extensions.gd.uid b/addons/twitcher/generated/twitch_get_user_extensions.gd.uid new file mode 100644 index 00000000..767bca2c --- /dev/null +++ b/addons/twitcher/generated/twitch_get_user_extensions.gd.uid @@ -0,0 +1 @@ +uid://dvienfsw61wj2 diff --git a/addons/twitcher/generated/twitch_get_users.gd b/addons/twitcher/generated/twitch_get_users.gd new file mode 100644 index 00000000..e4e2f81a --- /dev/null +++ b/addons/twitcher/generated/twitch_get_users.gd @@ -0,0 +1,71 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetUsers + + + +## +## #/components/schemas/GetUsersResponse +class Response extends TwitchData: + + ## The list of users. + @export var data: Array[TwitchUser]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUser]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUser.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.get_users +## #/components/schemas/GetUsersOpt +class Opt extends TwitchData: + + ## The ID of the user to get. To specify more than one user, include the _id_ parameter for each user to get. For example, `id=1234&id=5678`. The maximum number of IDs you may specify is 100. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The login name of the user to get. To specify more than one user, include the _login_ parameter for each user to get. For example, `login=foo&login=bar`. The maximum number of login names you may specify is 100. + @export var login: Array[String]: + set(val): + login = val + track_data(&"login", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("login", null) != null: + for value in d["login"]: + result.login.append(value) + return result + diff --git a/addons/twitcher/generated/twitch_get_users.gd.uid b/addons/twitcher/generated/twitch_get_users.gd.uid new file mode 100644 index 00000000..a4b9f7d6 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_users.gd.uid @@ -0,0 +1 @@ +uid://chbom8p4hqbvq diff --git a/addons/twitcher/generated/twitch_get_vi_ps.gd b/addons/twitcher/generated/twitch_get_vi_ps.gd new file mode 100644 index 00000000..ad2aa22b --- /dev/null +++ b/addons/twitcher/generated/twitch_get_vi_ps.gd @@ -0,0 +1,110 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetVIPs + + + +## +## #/components/schemas/GetVIPsResponse +class Response extends TwitchData: + + ## The list of VIPs. The list is empty if the broadcaster doesn’t have VIP users. + @export var data: Array[TwitchUserVip]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUserVip]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUserVip.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetVIPsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_vi_ps.gd.uid b/addons/twitcher/generated/twitch_get_vi_ps.gd.uid new file mode 100644 index 00000000..c87446dc --- /dev/null +++ b/addons/twitcher/generated/twitch_get_vi_ps.gd.uid @@ -0,0 +1 @@ +uid://b8cnn7h1vimui diff --git a/addons/twitcher/generated/twitch_get_videos.gd b/addons/twitcher/generated/twitch_get_videos.gd new file mode 100644 index 00000000..38521050 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_videos.gd @@ -0,0 +1,249 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetVideos + + + +## +## #/components/schemas/GetVideosResponse +class Response extends TwitchData: + + ## The list of published videos that match the filter criteria. + @export var data: Array[TwitchVideo]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchVideo]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchVideo.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/GetVideosResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request's _after_ or _before_ query parameter depending on whether you're paging forwards or backwards through the results. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.get_videos +## #/components/schemas/GetVideosOpt +class Opt extends TwitchData: + + ## A list of IDs that identify the videos you want to get. To get more than one video, include this parameter for each video you want to get. For example, `id=1234&id=5678`. You may specify a maximum of 100 IDs. The endpoint ignores duplicate IDs and IDs that weren't found (if there's at least one valid ID). + ## + ## The _id_, _user\_id_, and _game\_id_ parameters are mutually exclusive. + @export var id: Array[String]: + set(val): + id = val + track_data(&"id", val) + + ## The ID of the user whose list of videos you want to get. + ## + ## The _id_, _user\_id_, and _game\_id_ parameters are mutually exclusive. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## A category or game ID. The response contains a maximum of 500 videos that show this content. To get category/game IDs, use the [Search Categories](https://dev.twitch.tv/docs/api/reference#search-categories) endpoint. + ## + ## The _id_, _user\_id_, and _game\_id_ parameters are mutually exclusive. + @export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + + ## A filter used to filter the list of videos by the language that the video owner broadcasts in. For example, to get videos that were broadcast in German, set this parameter to the ISO 639-1 two-letter code for German (i.e., DE). For a list of supported languages, see [Supported Stream Language](https://help.twitch.tv/s/article/languages-on-twitch#streamlang). If the language is not supported, use “other.” + ## + ## Specify this parameter only if you specify the _game\_id_ query parameter. + @export var language: String: + set(val): + language = val + track_data(&"language", val) + + ## A filter used to filter the list of videos by when they were published. For example, videos published in the last week. Possible values are: + ## + ## * all + ## * day + ## * month + ## * week + ## + ## The default is "all," which returns videos published in all periods. + ## + ## Specify this parameter only if you specify the _game\_id_ or _user\_id_ query parameter. + @export var period: String: + set(val): + period = val + track_data(&"period", val) + + ## The order to sort the returned videos in. Possible values are: + ## + ## * time — Sort the results in descending order by when they were created (i.e., latest video first). + ## * trending — Sort the results in descending order by biggest gains in viewership (i.e., highest trending video first). + ## * views — Sort the results in descending order by most views (i.e., highest number of views first). + ## + ## The default is "time." + ## + ## Specify this parameter only if you specify the _game\_id_ or _user\_id_ query parameter. + @export var sort: String: + set(val): + sort = val + track_data(&"sort", val) + + ## A filter used to filter the list of videos by the video's type. Possible case-sensitive values are: + ## + ## * all + ## * archive — On-demand videos (VODs) of past streams. + ## * highlight — Highlight reels of past streams. + ## * upload — External videos that the broadcaster uploaded using the Video Producer. + ## + ## The default is "all," which returns all video types. + ## + ## Specify this parameter only if you specify the _game\_id_ or _user\_id_ query parameter. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + ## + ## Specify this parameter only if you specify the _game\_id_ or _user\_id_ query parameter. + @export var first: String: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + ## + ## Specify this parameter only if you specify the _user\_id_ query parameter. + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + ## The cursor used to get the previous page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + ## + ## Specify this parameter only if you specify the _user\_id_ query parameter. + @export var before: String: + set(val): + before = val + track_data(&"before", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("id", null) != null: + for value in d["id"]: + result.id.append(value) + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("language", null) != null: + result.language = d["language"] + if d.get("period", null) != null: + result.period = d["period"] + if d.get("sort", null) != null: + result.sort = d["sort"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + if d.get("before", null) != null: + result.before = d["before"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_videos.gd.uid b/addons/twitcher/generated/twitch_get_videos.gd.uid new file mode 100644 index 00000000..3ead0ea6 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_videos.gd.uid @@ -0,0 +1 @@ +uid://cvomjx2io3x8g diff --git a/addons/twitcher/generated/twitch_get_vips.gd b/addons/twitcher/generated/twitch_get_vips.gd new file mode 100644 index 00000000..da17b801 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_vips.gd @@ -0,0 +1,50 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchGetVips + + + +## All optional parameters for TwitchAPI.get_vips +## #/components/schemas/GetVipsOpt +class Opt extends TwitchData: + + ## Filters the list for specific VIPs. To specify more than one user, include the _user\_id_ parameter for each user to get. For example, `&user_id=1234&user_id=5678`. The maximum number of IDs that you may specify is 100\. Ignores the ID of those users in the list that aren’t VIPs. + @export var user_id: Array[String]: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100\. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("user_id", null) != null: + for value in d["user_id"]: + result.user_id.append(value) + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_get_vips.gd.uid b/addons/twitcher/generated/twitch_get_vips.gd.uid new file mode 100644 index 00000000..d66cc400 --- /dev/null +++ b/addons/twitcher/generated/twitch_get_vips.gd.uid @@ -0,0 +1 @@ +uid://nc2aban0weuf diff --git a/addons/twitcher/generated/twitch_global_emote.gd b/addons/twitcher/generated/twitch_global_emote.gd new file mode 100644 index 00000000..a6c2017b --- /dev/null +++ b/addons/twitcher/generated/twitch_global_emote.gd @@ -0,0 +1,137 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/GlobalEmote +class_name TwitchGlobalEmote + +## An ID that identifies this emote. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +@export var images: Images: + set(val): + images = val + track_data(&"images", val) + +## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only `static`. But if the emote is available as a static PNG and an animated GIF, the array contains `static` and `animated`. The possible formats are: +## +## * animated — An animated GIF is available for this emote. +## * static — A static PNG file is available for this emote. +@export var format: Array[String]: + set(val): + format = val + track_data(&"format", val) + +## The sizes that the emote is available in. For example, if the emote is available in small and medium sizes, the array contains 1.0 and 2.0\. Possible sizes are: +## +## * 1.0 — A small version (28px x 28px) is available. +## * 2.0 — A medium version (56px x 56px) is available. +## * 3.0 — A large version (112px x 112px) is available. +@export var scale: Array[String]: + set(val): + scale = val + track_data(&"scale", val) + +## The background themes that the emote is available in. Possible themes are: +## +## * dark +## * light +@export var theme_mode: Array[String]: + set(val): + theme_mode = val + track_data(&"theme_mode", val) + + + +## Constructor with all required fields. +static func create(_id: String, _name: String, _images: Images, _format: Array[String], _scale: Array[String], _theme_mode: Array[String]) -> TwitchGlobalEmote: + var twitch_global_emote: TwitchGlobalEmote = TwitchGlobalEmote.new() + twitch_global_emote.id = _id + twitch_global_emote.name = _name + twitch_global_emote.images = _images + twitch_global_emote.format = _format + twitch_global_emote.scale = _scale + twitch_global_emote.theme_mode = _theme_mode + return twitch_global_emote + + +static func from_json(d: Dictionary) -> TwitchGlobalEmote: + var result: TwitchGlobalEmote = TwitchGlobalEmote.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("images", null) != null: + result.images = Images.from_json(d["images"]) + if d.get("format", null) != null: + for value in d["format"]: + result.format.append(value) + if d.get("scale", null) != null: + for value in d["scale"]: + result.scale.append(value) + if d.get("theme_mode", null) != null: + for value in d["theme_mode"]: + result.theme_mode.append(value) + return result + + + +## The image URLs for the emote. These image URLs always provide a static, non-animated emote image with a light background. +## +## **NOTE:** You should use the templated URL in the `template` field to fetch the image instead of using these URLs. +## #/components/schemas/GlobalEmote/Images +class Images extends TwitchData: + + ## A URL to the small version (28px x 28px) of the emote. + @export var url_1x: String: + set(val): + url_1x = val + track_data(&"url_1x", val) + + ## A URL to the medium version (56px x 56px) of the emote. + @export var url_2x: String: + set(val): + url_2x = val + track_data(&"url_2x", val) + + ## A URL to the large version (112px x 112px) of the emote. + @export var url_4x: String: + set(val): + url_4x = val + track_data(&"url_4x", val) + + + + ## Constructor with all required fields. + static func create(_url_1x: String, _url_2x: String, _url_4x: String) -> Images: + var images: Images = Images.new() + images.url_1x = _url_1x + images.url_2x = _url_2x + images.url_4x = _url_4x + return images + + + static func from_json(d: Dictionary) -> Images: + var result: Images = Images.new() + if d.get("url_1x", null) != null: + result.url_1x = d["url_1x"] + if d.get("url_2x", null) != null: + result.url_2x = d["url_2x"] + if d.get("url_4x", null) != null: + result.url_4x = d["url_4x"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_global_emote.gd.uid b/addons/twitcher/generated/twitch_global_emote.gd.uid new file mode 100644 index 00000000..0d3fe7e5 --- /dev/null +++ b/addons/twitcher/generated/twitch_global_emote.gd.uid @@ -0,0 +1 @@ +uid://y5jlmdqski4v diff --git a/addons/twitcher/generated/twitch_guest.gd b/addons/twitcher/generated/twitch_guest.gd new file mode 100644 index 00000000..1b44a7a1 --- /dev/null +++ b/addons/twitcher/generated/twitch_guest.gd @@ -0,0 +1,197 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Guest +class_name TwitchGuest + +## ID representing this guest’s slot assignment. +## +## * Host is always in slot "0" +## * Guests are assigned the following consecutive IDs (e.g, "1", "2", "3", etc) +## * Screen Share is represented as a special guest with the ID "SCREENSHARE" +## * The identifier here matches the ID referenced in browser source links used in broadcasting software. +@export var slot_id: String: + set(val): + slot_id = val + track_data(&"slot_id", val) + +## Flag determining whether or not the guest is visible in the browser source in the host’s streaming software. +@export var is_live: bool: + set(val): + is_live = val + track_data(&"is_live", val) + +## User ID of the guest assigned to this slot. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## Display name of the guest assigned to this slot. +@export var user_display_name: String: + set(val): + user_display_name = val + track_data(&"user_display_name", val) + +## Login of the guest assigned to this slot. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## Value from 0 to 100 representing the host’s volume setting for this guest. +@export var volume: int: + set(val): + volume = val + track_data(&"volume", val) + +## Timestamp when this guest was assigned a slot in the session. +@export var assigned_at: String: + set(val): + assigned_at = val + track_data(&"assigned_at", val) + +## Information about the guest’s audio settings +@export var audio_settings: AudioSettings: + set(val): + audio_settings = val + track_data(&"audio_settings", val) + +## Information about the guest’s video settings +@export var video_settings: VideoSettings: + set(val): + video_settings = val + track_data(&"video_settings", val) + + + +## Constructor with all required fields. +static func create(_slot_id: String, _is_live: bool, _user_id: String, _user_display_name: String, _user_login: String, _volume: int, _assigned_at: String, _audio_settings: AudioSettings, _video_settings: VideoSettings) -> TwitchGuest: + var twitch_guest: TwitchGuest = TwitchGuest.new() + twitch_guest.slot_id = _slot_id + twitch_guest.is_live = _is_live + twitch_guest.user_id = _user_id + twitch_guest.user_display_name = _user_display_name + twitch_guest.user_login = _user_login + twitch_guest.volume = _volume + twitch_guest.assigned_at = _assigned_at + twitch_guest.audio_settings = _audio_settings + twitch_guest.video_settings = _video_settings + return twitch_guest + + +static func from_json(d: Dictionary) -> TwitchGuest: + var result: TwitchGuest = TwitchGuest.new() + if d.get("slot_id", null) != null: + result.slot_id = d["slot_id"] + if d.get("is_live", null) != null: + result.is_live = d["is_live"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_display_name", null) != null: + result.user_display_name = d["user_display_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("volume", null) != null: + result.volume = d["volume"] + if d.get("assigned_at", null) != null: + result.assigned_at = d["assigned_at"] + if d.get("audio_settings", null) != null: + result.audio_settings = AudioSettings.from_json(d["audio_settings"]) + if d.get("video_settings", null) != null: + result.video_settings = VideoSettings.from_json(d["video_settings"]) + return result + + + +## Information about the guest’s audio settings +## #/components/schemas/Guest/AudioSettings +class AudioSettings extends TwitchData: + + ## Flag determining whether the host is allowing the guest’s audio to be seen or heard within the session. + @export var is_host_enabled: bool: + set(val): + is_host_enabled = val + track_data(&"is_host_enabled", val) + + ## Flag determining whether the guest is allowing their audio to be transmitted to the session. + @export var is_guest_enabled: bool: + set(val): + is_guest_enabled = val + track_data(&"is_guest_enabled", val) + + ## Flag determining whether the guest has an appropriate audio device available to be transmitted to the session. + @export var is_available: bool: + set(val): + is_available = val + track_data(&"is_available", val) + + + + ## Constructor with all required fields. + static func create(_is_host_enabled: bool, _is_guest_enabled: bool, _is_available: bool) -> AudioSettings: + var audio_settings: AudioSettings = AudioSettings.new() + audio_settings.is_host_enabled = _is_host_enabled + audio_settings.is_guest_enabled = _is_guest_enabled + audio_settings.is_available = _is_available + return audio_settings + + + static func from_json(d: Dictionary) -> AudioSettings: + var result: AudioSettings = AudioSettings.new() + if d.get("is_host_enabled", null) != null: + result.is_host_enabled = d["is_host_enabled"] + if d.get("is_guest_enabled", null) != null: + result.is_guest_enabled = d["is_guest_enabled"] + if d.get("is_available", null) != null: + result.is_available = d["is_available"] + return result + + + +## Information about the guest’s video settings +## #/components/schemas/Guest/VideoSettings +class VideoSettings extends TwitchData: + + ## Flag determining whether the host is allowing the guest’s video to be seen or heard within the session. + @export var is_host_enabled: bool: + set(val): + is_host_enabled = val + track_data(&"is_host_enabled", val) + + ## Flag determining whether the guest is allowing their video to be transmitted to the session. + @export var is_guest_enabled: bool: + set(val): + is_guest_enabled = val + track_data(&"is_guest_enabled", val) + + ## Flag determining whether the guest has an appropriate video device available to be transmitted to the session. + @export var is_available: bool: + set(val): + is_available = val + track_data(&"is_available", val) + + + + ## Constructor with all required fields. + static func create(_is_host_enabled: bool, _is_guest_enabled: bool, _is_available: bool) -> VideoSettings: + var video_settings: VideoSettings = VideoSettings.new() + video_settings.is_host_enabled = _is_host_enabled + video_settings.is_guest_enabled = _is_guest_enabled + video_settings.is_available = _is_available + return video_settings + + + static func from_json(d: Dictionary) -> VideoSettings: + var result: VideoSettings = VideoSettings.new() + if d.get("is_host_enabled", null) != null: + result.is_host_enabled = d["is_host_enabled"] + if d.get("is_guest_enabled", null) != null: + result.is_guest_enabled = d["is_guest_enabled"] + if d.get("is_available", null) != null: + result.is_available = d["is_available"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_guest.gd.uid b/addons/twitcher/generated/twitch_guest.gd.uid new file mode 100644 index 00000000..5665737c --- /dev/null +++ b/addons/twitcher/generated/twitch_guest.gd.uid @@ -0,0 +1 @@ +uid://r51io6o4te3i diff --git a/addons/twitcher/generated/twitch_guest_star_invite.gd b/addons/twitcher/generated/twitch_guest_star_invite.gd new file mode 100644 index 00000000..447fd779 --- /dev/null +++ b/addons/twitcher/generated/twitch_guest_star_invite.gd @@ -0,0 +1,87 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/GuestStarInvite +class_name TwitchGuestStarInvite + +## Twitch User ID corresponding to the invited guest +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## Timestamp when this user was invited to the session. +@export var invited_at: String: + set(val): + invited_at = val + track_data(&"invited_at", val) + +## Status representing the invited user’s join state. Can be one of the following: +## +## * `INVITED`: The user has been invited to the session but has not acknowledged it. +## * `ACCEPTED`: The invited user has acknowledged the invite and joined the waiting room, but may still be setting up their media devices or otherwise preparing to join the call. +## * `READY`: The invited user has signaled they are ready to join the call from the waiting room. +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## Flag signaling that the invited user has chosen to disable their local video device. The user has hidden themselves, but they may choose to reveal their video feed upon joining the session. +@export var is_video_enabled: bool: + set(val): + is_video_enabled = val + track_data(&"is_video_enabled", val) + +## Flag signaling that the invited user has chosen to disable their local audio device. The user has muted themselves, but they may choose to unmute their audio feed upon joining the session. +@export var is_audio_enabled: bool: + set(val): + is_audio_enabled = val + track_data(&"is_audio_enabled", val) + +## Flag signaling that the invited user has a video device available for sharing. +@export var is_video_available: bool: + set(val): + is_video_available = val + track_data(&"is_video_available", val) + +## Flag signaling that the invited user has an audio device available for sharing. +@export var is_audio_available: bool: + set(val): + is_audio_available = val + track_data(&"is_audio_available", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _invited_at: String, _status: String, _is_video_enabled: bool, _is_audio_enabled: bool, _is_video_available: bool, _is_audio_available: bool) -> TwitchGuestStarInvite: + var twitch_guest_star_invite: TwitchGuestStarInvite = TwitchGuestStarInvite.new() + twitch_guest_star_invite.user_id = _user_id + twitch_guest_star_invite.invited_at = _invited_at + twitch_guest_star_invite.status = _status + twitch_guest_star_invite.is_video_enabled = _is_video_enabled + twitch_guest_star_invite.is_audio_enabled = _is_audio_enabled + twitch_guest_star_invite.is_video_available = _is_video_available + twitch_guest_star_invite.is_audio_available = _is_audio_available + return twitch_guest_star_invite + + +static func from_json(d: Dictionary) -> TwitchGuestStarInvite: + var result: TwitchGuestStarInvite = TwitchGuestStarInvite.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("invited_at", null) != null: + result.invited_at = d["invited_at"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("is_video_enabled", null) != null: + result.is_video_enabled = d["is_video_enabled"] + if d.get("is_audio_enabled", null) != null: + result.is_audio_enabled = d["is_audio_enabled"] + if d.get("is_video_available", null) != null: + result.is_video_available = d["is_video_available"] + if d.get("is_audio_available", null) != null: + result.is_audio_available = d["is_audio_available"] + return result diff --git a/addons/twitcher/generated/twitch_guest_star_invite.gd.uid b/addons/twitcher/generated/twitch_guest_star_invite.gd.uid new file mode 100644 index 00000000..da6ffddb --- /dev/null +++ b/addons/twitcher/generated/twitch_guest_star_invite.gd.uid @@ -0,0 +1 @@ +uid://ba0i6iveocqcg diff --git a/addons/twitcher/generated/twitch_guest_star_session.gd b/addons/twitcher/generated/twitch_guest_star_session.gd new file mode 100644 index 00000000..726c8fda --- /dev/null +++ b/addons/twitcher/generated/twitch_guest_star_session.gd @@ -0,0 +1,39 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/GuestStarSession +class_name TwitchGuestStarSession + +## ID uniquely representing the Guest Star session. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## List of guests currently interacting with the Guest Star session. +@export var guests: Array[TwitchGuest]: + set(val): + guests = val + track_data(&"guests", val) + + + +## Constructor with all required fields. +static func create(_id: String, _guests: Array[TwitchGuest]) -> TwitchGuestStarSession: + var twitch_guest_star_session: TwitchGuestStarSession = TwitchGuestStarSession.new() + twitch_guest_star_session.id = _id + twitch_guest_star_session.guests = _guests + return twitch_guest_star_session + + +static func from_json(d: Dictionary) -> TwitchGuestStarSession: + var result: TwitchGuestStarSession = TwitchGuestStarSession.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("guests", null) != null: + for value in d["guests"]: + result.guests.append(TwitchGuest.from_json(value)) + return result diff --git a/addons/twitcher/generated/twitch_guest_star_session.gd.uid b/addons/twitcher/generated/twitch_guest_star_session.gd.uid new file mode 100644 index 00000000..9d16d6c1 --- /dev/null +++ b/addons/twitcher/generated/twitch_guest_star_session.gd.uid @@ -0,0 +1 @@ +uid://dd3j3aobm1p2t diff --git a/addons/twitcher/generated/twitch_hype_train_event.gd b/addons/twitcher/generated/twitch_hype_train_event.gd new file mode 100644 index 00000000..cf0936b0 --- /dev/null +++ b/addons/twitcher/generated/twitch_hype_train_event.gd @@ -0,0 +1,273 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/HypeTrainEvent +class_name TwitchHypeTrainEvent + +## An ID that identifies this event. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The type of event. The string is in the form, hypetrain.{event\_name}. The request returns only progress event types (i.e., hypetrain.progression). +@export var event_type: String: + set(val): + event_type = val + track_data(&"event_type", val) + +## The UTC date and time (in RFC3339 format) that the event occurred. +@export var event_timestamp: String: + set(val): + event_timestamp = val + track_data(&"event_timestamp", val) + +## The version number of the definition of the event’s data. For example, the value is 1 if the data in `event_data` uses the first definition of the event’s data. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The event’s data. +@export var event_data: EventData: + set(val): + event_data = val + track_data(&"event_data", val) + + + +## Constructor with all required fields. +static func create(_id: String, _event_type: String, _event_timestamp: String, _version: String, _event_data: EventData) -> TwitchHypeTrainEvent: + var twitch_hype_train_event: TwitchHypeTrainEvent = TwitchHypeTrainEvent.new() + twitch_hype_train_event.id = _id + twitch_hype_train_event.event_type = _event_type + twitch_hype_train_event.event_timestamp = _event_timestamp + twitch_hype_train_event.version = _version + twitch_hype_train_event.event_data = _event_data + return twitch_hype_train_event + + +static func from_json(d: Dictionary) -> TwitchHypeTrainEvent: + var result: TwitchHypeTrainEvent = TwitchHypeTrainEvent.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("event_type", null) != null: + result.event_type = d["event_type"] + if d.get("event_timestamp", null) != null: + result.event_timestamp = d["event_timestamp"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("event_data", null) != null: + result.event_data = EventData.from_json(d["event_data"]) + return result + + + +## The event’s data. +## #/components/schemas/HypeTrainEvent/EventData +class EventData extends TwitchData: + + ## The ID of the broadcaster that’s running the Hype Train. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The UTC date and time (in RFC3339 format) that another Hype Train can start. + @export var cooldown_end_time: String: + set(val): + cooldown_end_time = val + track_data(&"cooldown_end_time", val) + + ## The UTC date and time (in RFC3339 format) that the Hype Train ends. + @export var expires_at: String: + set(val): + expires_at = val + track_data(&"expires_at", val) + + ## The value needed to reach the next level. + @export var goal: int: + set(val): + goal = val + track_data(&"goal", val) + + ## An ID that identifies this Hype Train. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The most recent contribution towards the Hype Train’s goal. + @export var last_contribution: LastContribution: + set(val): + last_contribution = val + track_data(&"last_contribution", val) + + ## The highest level that the Hype Train reached (the levels are 1 through 5). + @export var level: int: + set(val): + level = val + track_data(&"level", val) + + ## The UTC date and time (in RFC3339 format) that this Hype Train started. + @export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + + ## The top contributors for each contribution type. For example, the top contributor using BITS (by aggregate) and the top contributor using SUBS (by count). + @export var top_contributions: Array[TopContributions]: + set(val): + top_contributions = val + track_data(&"top_contributions", val) + + ## The current total amount raised. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _cooldown_end_time: String, _expires_at: String, _goal: int, _id: String, _last_contribution: LastContribution, _level: int, _started_at: String, _top_contributions: Array[TopContributions], _total: int) -> EventData: + var event_data: EventData = EventData.new() + event_data.broadcaster_id = _broadcaster_id + event_data.cooldown_end_time = _cooldown_end_time + event_data.expires_at = _expires_at + event_data.goal = _goal + event_data.id = _id + event_data.last_contribution = _last_contribution + event_data.level = _level + event_data.started_at = _started_at + event_data.top_contributions = _top_contributions + event_data.total = _total + return event_data + + + static func from_json(d: Dictionary) -> EventData: + var result: EventData = EventData.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("cooldown_end_time", null) != null: + result.cooldown_end_time = d["cooldown_end_time"] + if d.get("expires_at", null) != null: + result.expires_at = d["expires_at"] + if d.get("goal", null) != null: + result.goal = d["goal"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("last_contribution", null) != null: + result.last_contribution = LastContribution.from_json(d["last_contribution"]) + if d.get("level", null) != null: + result.level = d["level"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("top_contributions", null) != null: + for value in d["top_contributions"]: + result.top_contributions.append(TopContributions.from_json(value)) + if d.get("total", null) != null: + result.total = d["total"] + return result + + + +## The most recent contribution towards the Hype Train’s goal. +## #/components/schemas/HypeTrainEvent/EventData/LastContribution +class LastContribution extends TwitchData: + + ## The total amount contributed. If `type` is BITS, `total` represents the amount of Bits used. If `type` is SUBS, `total` is 500, 1000, or 2500 to represent tier 1, 2, or 3 subscriptions, respectively. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The contribution method used. Possible values are: + ## + ## * BITS — Cheering with Bits. + ## * SUBS — Subscription activity like subscribing or gifting subscriptions. + ## * OTHER — Covers other contribution methods not listed. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The ID of the user that made the contribution. + @export var user: String: + set(val): + user = val + track_data(&"user", val) + + + + ## Constructor with all required fields. + static func create(_total: int, _type: String, _user: String) -> LastContribution: + var last_contribution: LastContribution = LastContribution.new() + last_contribution.total = _total + last_contribution.type = _type + last_contribution.user = _user + return last_contribution + + + static func from_json(d: Dictionary) -> LastContribution: + var result: LastContribution = LastContribution.new() + if d.get("total", null) != null: + result.total = d["total"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("user", null) != null: + result.user = d["user"] + return result + + + +## The top contributors for each contribution type. For example, the top contributor using BITS (by aggregate) and the top contributor using SUBS (by count). +## #/components/schemas/HypeTrainEvent/EventData/TopContributions +class TopContributions extends TwitchData: + + ## The total amount contributed. If `type` is BITS, `total` represents the amount of Bits used. If `type` is SUBS, `total` is 500, 1000, or 2500 to represent tier 1, 2, or 3 subscriptions, respectively. + @export var total: int: + set(val): + total = val + track_data(&"total", val) + + ## The contribution method used. Possible values are: + ## + ## * BITS — Cheering with Bits. + ## * SUBS — Subscription activity like subscribing or gifting subscriptions. + ## * OTHER — Covers other contribution methods not listed. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + ## The ID of the user that made the contribution. + @export var user: String: + set(val): + user = val + track_data(&"user", val) + + + + ## Constructor with all required fields. + static func create(_total: int, _type: String, _user: String) -> TopContributions: + var top_contributions: TopContributions = TopContributions.new() + top_contributions.total = _total + top_contributions.type = _type + top_contributions.user = _user + return top_contributions + + + static func from_json(d: Dictionary) -> TopContributions: + var result: TopContributions = TopContributions.new() + if d.get("total", null) != null: + result.total = d["total"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("user", null) != null: + result.user = d["user"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_hype_train_event.gd.uid b/addons/twitcher/generated/twitch_hype_train_event.gd.uid new file mode 100644 index 00000000..cf6b30a9 --- /dev/null +++ b/addons/twitcher/generated/twitch_hype_train_event.gd.uid @@ -0,0 +1 @@ +uid://dd8nh686wvbie diff --git a/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd b/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd new file mode 100644 index 00000000..a2fc9fb3 --- /dev/null +++ b/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd @@ -0,0 +1,55 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchManageHeldAutoModMessages + + + +## +## #/components/schemas/ManageHeldAutoModMessagesBody +class Body extends TwitchData: + + ## The moderator who is approving or denying the held message. This ID must match the user ID in the access token. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The ID of the message to allow or deny. + @export var msg_id: String: + set(val): + msg_id = val + track_data(&"msg_id", val) + + ## The action to take for the message. Possible values are: + ## + ## * ALLOW + ## * DENY + @export var action: String: + set(val): + action = val + track_data(&"action", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String, _msg_id: String, _action: String) -> Body: + var body: Body = Body.new() + body.user_id = _user_id + body.msg_id = _msg_id + body.action = _action + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("msg_id", null) != null: + result.msg_id = d["msg_id"] + if d.get("action", null) != null: + result.action = d["action"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd.uid b/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd.uid new file mode 100644 index 00000000..f70f464e --- /dev/null +++ b/addons/twitcher/generated/twitch_manage_held_auto_mod_messages.gd.uid @@ -0,0 +1 @@ +uid://blw16lh307ick diff --git a/addons/twitcher/generated/twitch_modify_channel_information.gd b/addons/twitcher/generated/twitch_modify_channel_information.gd new file mode 100644 index 00000000..813e84a9 --- /dev/null +++ b/addons/twitcher/generated/twitch_modify_channel_information.gd @@ -0,0 +1,128 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchModifyChannelInformation + + + +## +## #/components/schemas/ModifyChannelInformationBody +class Body extends TwitchData: + + ## The ID of the game that the user plays. The game is not updated if the ID isn’t a game ID that Twitch recognizes. To unset this field, use “0” or “” (an empty string). + @export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + + ## The user’s preferred language. Set the value to an ISO 639-1 two-letter language code (for example, _en_ for English). Set to “other” if the user’s preferred language is not a Twitch supported language. The language isn’t updated if the language code isn’t a Twitch supported language. + @export var broadcaster_language: String: + set(val): + broadcaster_language = val + track_data(&"broadcaster_language", val) + + ## The title of the user’s stream. You may not set this field to an empty string. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The number of seconds you want your broadcast buffered before streaming it live. The delay helps ensure fairness during competitive play. Only users with Partner status may set this field. The maximum delay is 900 seconds (15 minutes). + @export var delay: int: + set(val): + delay = val + track_data(&"delay", val) + + ## A list of channel-defined tags to apply to the channel. To remove all tags from the channel, set tags to an empty array. Tags help identify the content that the channel streams. [Learn More](https://help.twitch.tv/s/article/guide-to-tags) + ## + ## A channel may specify a maximum of 10 tags. Each tag is limited to a maximum of 25 characters and may not be an empty string or contain spaces or special characters. Tags are case insensitive. For readability, consider using camelCasing or PascalCasing. + @export var tags: Array[String]: + set(val): + tags = val + track_data(&"tags", val) + + ## List of labels that should be set as the Channel’s CCLs. + @export var content_classification_labels: Array[BodyContentClassificationLabels]: + set(val): + content_classification_labels = val + track_data(&"content_classification_labels", val) + + ## Boolean flag indicating if the channel has branded content. + @export var is_branded_content: bool: + set(val): + is_branded_content = val + track_data(&"is_branded_content", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("broadcaster_language", null) != null: + result.broadcaster_language = d["broadcaster_language"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("delay", null) != null: + result.delay = d["delay"] + if d.get("tags", null) != null: + for value in d["tags"]: + result.tags.append(value) + if d.get("content_classification_labels", null) != null: + for value in d["content_classification_labels"]: + result.content_classification_labels.append(BodyContentClassificationLabels.from_json(value)) + if d.get("is_branded_content", null) != null: + result.is_branded_content = d["is_branded_content"] + return result + + + +## List of labels that should be set as the Channel’s CCLs. +## #/components/schemas/ModifyChannelInformationBody/ContentClassificationLabels +class BodyContentClassificationLabels extends TwitchData: + + ## ID of the [Content Classification Labels](https://help.twitch.tv/s/article/content-classification-labels) that must be added/removed from the channel. Can be one of the following values: + ## + ## * DebatedSocialIssuesAndPolitics + ## * DrugsIntoxication + ## * SexualThemes + ## * ViolentGraphic + ## * Gambling + ## * ProfanityVulgarity + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## Boolean flag indicating whether the label should be enabled (true) or disabled for the channel. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _is_enabled: bool) -> BodyContentClassificationLabels: + var body_content_classification_labels: BodyContentClassificationLabels = BodyContentClassificationLabels.new() + body_content_classification_labels.id = _id + body_content_classification_labels.is_enabled = _is_enabled + return body_content_classification_labels + + + static func from_json(d: Dictionary) -> BodyContentClassificationLabels: + var result: BodyContentClassificationLabels = BodyContentClassificationLabels.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_modify_channel_information.gd.uid b/addons/twitcher/generated/twitch_modify_channel_information.gd.uid new file mode 100644 index 00000000..7f459d31 --- /dev/null +++ b/addons/twitcher/generated/twitch_modify_channel_information.gd.uid @@ -0,0 +1 @@ +uid://b2fyqvrox2xvl diff --git a/addons/twitcher/generated/twitch_poll.gd b/addons/twitcher/generated/twitch_poll.gd new file mode 100644 index 00000000..db99424a --- /dev/null +++ b/addons/twitcher/generated/twitch_poll.gd @@ -0,0 +1,218 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Poll +class_name TwitchPoll + +## An ID that identifies the poll. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the broadcaster that created the poll. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The question that viewers are voting on. For example, _What game should I play next?_ The title may contain a maximum of 60 characters. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## A list of choices that viewers can choose from. The list will contain a minimum of two choices and up to a maximum of five choices. +@export var choices: Array[Choices]: + set(val): + choices = val + track_data(&"choices", val) + +## Not used; will be set to **false**. +@export var bits_voting_enabled: bool: + set(val): + bits_voting_enabled = val + track_data(&"bits_voting_enabled", val) + +## Not used; will be set to 0. +@export var bits_per_vote: int: + set(val): + bits_per_vote = val + track_data(&"bits_per_vote", val) + +## A Boolean value that indicates whether viewers may cast additional votes using Channel Points. For information about Channel Points, see [Channel Points Guide](https://help.twitch.tv/s/article/channel-points-guide). +@export var channel_points_voting_enabled: bool: + set(val): + channel_points_voting_enabled = val + track_data(&"channel_points_voting_enabled", val) + +## The number of points the viewer must spend to cast one additional vote. +@export var channel_points_per_vote: int: + set(val): + channel_points_per_vote = val + track_data(&"channel_points_per_vote", val) + +## The poll’s status. Valid values are: +## +## * ACTIVE — The poll is running. +## * COMPLETED — The poll ended on schedule (see the `duration` field). +## * TERMINATED — The poll was terminated before its scheduled end. +## * ARCHIVED — The poll has been archived and is no longer visible on the channel. +## * MODERATED — The poll was deleted. +## * INVALID — Something went wrong while determining the state. +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## The length of time (in seconds) that the poll will run for. +@export var duration: int: + set(val): + duration = val + track_data(&"duration", val) + +## The UTC date and time (in RFC3339 format) of when the poll began. +@export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + +## The UTC date and time (in RFC3339 format) of when the poll ended. If `status` is ACTIVE, this field is set to **null**. +@export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + + + +## Constructor with all required fields. +static func create(_id: String, _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _title: String, _choices: Array[Choices], _bits_voting_enabled: bool, _bits_per_vote: int, _channel_points_voting_enabled: bool, _channel_points_per_vote: int, _status: String, _duration: int, _started_at: String, _ended_at: String) -> TwitchPoll: + var twitch_poll: TwitchPoll = TwitchPoll.new() + twitch_poll.id = _id + twitch_poll.broadcaster_id = _broadcaster_id + twitch_poll.broadcaster_name = _broadcaster_name + twitch_poll.broadcaster_login = _broadcaster_login + twitch_poll.title = _title + twitch_poll.choices = _choices + twitch_poll.bits_voting_enabled = _bits_voting_enabled + twitch_poll.bits_per_vote = _bits_per_vote + twitch_poll.channel_points_voting_enabled = _channel_points_voting_enabled + twitch_poll.channel_points_per_vote = _channel_points_per_vote + twitch_poll.status = _status + twitch_poll.duration = _duration + twitch_poll.started_at = _started_at + twitch_poll.ended_at = _ended_at + return twitch_poll + + +static func from_json(d: Dictionary) -> TwitchPoll: + var result: TwitchPoll = TwitchPoll.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("choices", null) != null: + for value in d["choices"]: + result.choices.append(Choices.from_json(value)) + if d.get("bits_voting_enabled", null) != null: + result.bits_voting_enabled = d["bits_voting_enabled"] + if d.get("bits_per_vote", null) != null: + result.bits_per_vote = d["bits_per_vote"] + if d.get("channel_points_voting_enabled", null) != null: + result.channel_points_voting_enabled = d["channel_points_voting_enabled"] + if d.get("channel_points_per_vote", null) != null: + result.channel_points_per_vote = d["channel_points_per_vote"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + return result + + + +## A list of choices that viewers can choose from. The list will contain a minimum of two choices and up to a maximum of five choices. +## #/components/schemas/Poll/Choices +class Choices extends TwitchData: + + ## An ID that identifies this choice. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The choice’s title. The title may contain a maximum of 25 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The total number of votes cast for this choice. + @export var votes: int: + set(val): + votes = val + track_data(&"votes", val) + + ## The number of votes cast using Channel Points. + @export var channel_points_votes: int: + set(val): + channel_points_votes = val + track_data(&"channel_points_votes", val) + + ## Not used; will be set to 0. + @export var bits_votes: int: + set(val): + bits_votes = val + track_data(&"bits_votes", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _title: String, _votes: int, _channel_points_votes: int, _bits_votes: int) -> Choices: + var choices: Choices = Choices.new() + choices.id = _id + choices.title = _title + choices.votes = _votes + choices.channel_points_votes = _channel_points_votes + choices.bits_votes = _bits_votes + return choices + + + static func from_json(d: Dictionary) -> Choices: + var result: Choices = Choices.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("votes", null) != null: + result.votes = d["votes"] + if d.get("channel_points_votes", null) != null: + result.channel_points_votes = d["channel_points_votes"] + if d.get("bits_votes", null) != null: + result.bits_votes = d["bits_votes"] + return result + diff --git a/addons/twitcher/generated/twitch_poll.gd.uid b/addons/twitcher/generated/twitch_poll.gd.uid new file mode 100644 index 00000000..b34e2f2d --- /dev/null +++ b/addons/twitcher/generated/twitch_poll.gd.uid @@ -0,0 +1 @@ +uid://c4571q6e06upy diff --git a/addons/twitcher/generated/twitch_prediction.gd b/addons/twitcher/generated/twitch_prediction.gd new file mode 100644 index 00000000..cdcf6762 --- /dev/null +++ b/addons/twitcher/generated/twitch_prediction.gd @@ -0,0 +1,134 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Prediction +class_name TwitchPrediction + +## An ID that identifies this prediction. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## An ID that identifies the broadcaster that created the prediction. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The question that the prediction asks. For example, _Will I finish this entire pizza?_ +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The ID of the winning outcome. Is **null** unless `status` is RESOLVED. +@export var winning_outcome_id: String: + set(val): + winning_outcome_id = val + track_data(&"winning_outcome_id", val) + +## The list of possible outcomes for the prediction. +@export var outcomes: Array[TwitchPredictionOutcome]: + set(val): + outcomes = val + track_data(&"outcomes", val) + +## The length of time (in seconds) that the prediction will run for. +@export var prediction_window: int: + set(val): + prediction_window = val + track_data(&"prediction_window", val) + +## The prediction’s status. Valid values are: +## +## * ACTIVE — The Prediction is running and viewers can make predictions. +## * CANCELED — The broadcaster canceled the Prediction and refunded the Channel Points to the participants. +## * LOCKED — The broadcaster locked the Prediction, which means viewers can no longer make predictions. +## * RESOLVED — The winning outcome was determined and the Channel Points were distributed to the viewers who predicted the correct outcome. +@export var status: String: + set(val): + status = val + track_data(&"status", val) + +## The UTC date and time of when the Prediction began. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The UTC date and time of when the Prediction ended. If `status` is ACTIVE, this is set to **null**. +@export var ended_at: String: + set(val): + ended_at = val + track_data(&"ended_at", val) + +## The UTC date and time of when the Prediction was locked. If `status` is not LOCKED, this is set to **null**. +@export var locked_at: String: + set(val): + locked_at = val + track_data(&"locked_at", val) + + + +## Constructor with all required fields. +static func create(_id: String, _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _title: String, _winning_outcome_id: String, _outcomes: Array[TwitchPredictionOutcome], _prediction_window: int, _status: String, _created_at: String, _ended_at: String, _locked_at: String) -> TwitchPrediction: + var twitch_prediction: TwitchPrediction = TwitchPrediction.new() + twitch_prediction.id = _id + twitch_prediction.broadcaster_id = _broadcaster_id + twitch_prediction.broadcaster_name = _broadcaster_name + twitch_prediction.broadcaster_login = _broadcaster_login + twitch_prediction.title = _title + twitch_prediction.winning_outcome_id = _winning_outcome_id + twitch_prediction.outcomes = _outcomes + twitch_prediction.prediction_window = _prediction_window + twitch_prediction.status = _status + twitch_prediction.created_at = _created_at + twitch_prediction.ended_at = _ended_at + twitch_prediction.locked_at = _locked_at + return twitch_prediction + + +static func from_json(d: Dictionary) -> TwitchPrediction: + var result: TwitchPrediction = TwitchPrediction.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("winning_outcome_id", null) != null: + result.winning_outcome_id = d["winning_outcome_id"] + if d.get("outcomes", null) != null: + for value in d["outcomes"]: + result.outcomes.append(TwitchPredictionOutcome.from_json(value)) + if d.get("prediction_window", null) != null: + result.prediction_window = d["prediction_window"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("ended_at", null) != null: + result.ended_at = d["ended_at"] + if d.get("locked_at", null) != null: + result.locked_at = d["locked_at"] + return result diff --git a/addons/twitcher/generated/twitch_prediction.gd.uid b/addons/twitcher/generated/twitch_prediction.gd.uid new file mode 100644 index 00000000..36affd50 --- /dev/null +++ b/addons/twitcher/generated/twitch_prediction.gd.uid @@ -0,0 +1 @@ +uid://s4i670p4bu33 diff --git a/addons/twitcher/generated/twitch_prediction_outcome.gd b/addons/twitcher/generated/twitch_prediction_outcome.gd new file mode 100644 index 00000000..2be509ea --- /dev/null +++ b/addons/twitcher/generated/twitch_prediction_outcome.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/PredictionOutcome +class_name TwitchPredictionOutcome + +## An ID that identifies this outcome. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The outcome’s text. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The number of unique viewers that chose this outcome. +@export var users: int: + set(val): + users = val + track_data(&"users", val) + +## The number of Channel Points spent by viewers on this outcome. +@export var channel_points: int: + set(val): + channel_points = val + track_data(&"channel_points", val) + +## A list of viewers who were the top predictors; otherwise, **null** if none. +@export var top_predictors: Array[TopPredictors]: + set(val): + top_predictors = val + track_data(&"top_predictors", val) + +## The color that visually identifies this outcome in the UX. Possible values are: +## +## * BLUE +## * PINK +## +## If the number of outcomes is two, the color is BLUE for the first outcome and PINK for the second outcome. If there are more than two outcomes, the color is BLUE for all outcomes. +@export var color: String: + set(val): + color = val + track_data(&"color", val) + + + +## Constructor with all required fields. +static func create(_id: String, _title: String, _users: int, _channel_points: int, _top_predictors: Array[TopPredictors], _color: String) -> TwitchPredictionOutcome: + var twitch_prediction_outcome: TwitchPredictionOutcome = TwitchPredictionOutcome.new() + twitch_prediction_outcome.id = _id + twitch_prediction_outcome.title = _title + twitch_prediction_outcome.users = _users + twitch_prediction_outcome.channel_points = _channel_points + twitch_prediction_outcome.top_predictors = _top_predictors + twitch_prediction_outcome.color = _color + return twitch_prediction_outcome + + +static func from_json(d: Dictionary) -> TwitchPredictionOutcome: + var result: TwitchPredictionOutcome = TwitchPredictionOutcome.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("users", null) != null: + result.users = d["users"] + if d.get("channel_points", null) != null: + result.channel_points = d["channel_points"] + if d.get("top_predictors", null) != null: + for value in d["top_predictors"]: + result.top_predictors.append(TopPredictors.from_json(value)) + if d.get("color", null) != null: + result.color = d["color"] + return result + + + +## A list of viewers who were the top predictors; otherwise, **null** if none. +## #/components/schemas/PredictionOutcome/TopPredictors +class TopPredictors extends TwitchData: + + ## An ID that identifies the viewer. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The viewer’s display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + ## The viewer’s login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The number of Channel Points the viewer spent. + @export var channel_points_used: int: + set(val): + channel_points_used = val + track_data(&"channel_points_used", val) + + ## The number of Channel Points distributed to the viewer. + @export var channel_points_won: int: + set(val): + channel_points_won = val + track_data(&"channel_points_won", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String, _user_name: String, _user_login: String, _channel_points_used: int, _channel_points_won: int) -> TopPredictors: + var top_predictors: TopPredictors = TopPredictors.new() + top_predictors.user_id = _user_id + top_predictors.user_name = _user_name + top_predictors.user_login = _user_login + top_predictors.channel_points_used = _channel_points_used + top_predictors.channel_points_won = _channel_points_won + return top_predictors + + + static func from_json(d: Dictionary) -> TopPredictors: + var result: TopPredictors = TopPredictors.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("channel_points_used", null) != null: + result.channel_points_used = d["channel_points_used"] + if d.get("channel_points_won", null) != null: + result.channel_points_won = d["channel_points_won"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_prediction_outcome.gd.uid b/addons/twitcher/generated/twitch_prediction_outcome.gd.uid new file mode 100644 index 00000000..00d7df1c --- /dev/null +++ b/addons/twitcher/generated/twitch_prediction_outcome.gd.uid @@ -0,0 +1 @@ +uid://t5p7p3s20fyc diff --git a/addons/twitcher/generated/twitch_resolve_unban_requests.gd b/addons/twitcher/generated/twitch_resolve_unban_requests.gd new file mode 100644 index 00000000..b7bd7c91 --- /dev/null +++ b/addons/twitcher/generated/twitch_resolve_unban_requests.gd @@ -0,0 +1,217 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchResolveUnbanRequests + + + +## +## #/components/schemas/ResolveUnbanRequestsResponse +class Response extends TwitchData: + + ## + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## +## #/components/schemas/ResolveUnbanRequestsResponse/Data +class ResponseData extends TwitchData: + + ## Unban request ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## User ID of broadcaster whose channel is receiving the unban request. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The broadcaster’s display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## User ID of moderator who approved/denied the request. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The moderator’s login name. + @export var moderator_login: String: + set(val): + moderator_login = val + track_data(&"moderator_login", val) + + ## The moderator’s display name. + @export var moderator_name: String: + set(val): + moderator_name = val + track_data(&"moderator_name", val) + + ## User ID of the requestor who is asking for an unban. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The user’s login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The user’s display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + ## Text of the request from the requesting user. + @export var text: String: + set(val): + text = val + track_data(&"text", val) + + ## Status of the request. One of: + ## + ## * approved + ## * denied + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## Timestamp of when the unban request was created. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## Timestamp of when moderator/broadcaster approved or denied the request. + @export var resolved_at: String: + set(val): + resolved_at = val + track_data(&"resolved_at", val) + + ## Text input by the resolver (moderator) of the unban request. + @export var resolution_text: String: + set(val): + resolution_text = val + track_data(&"resolution_text", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _moderator_id: String, _moderator_login: String, _moderator_name: String, _user_id: String, _user_login: String, _user_name: String, _text: String, _status: String, _created_at: String, _resolved_at: String, _resolution_text: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_login = _broadcaster_login + response_data.broadcaster_name = _broadcaster_name + response_data.moderator_id = _moderator_id + response_data.moderator_login = _moderator_login + response_data.moderator_name = _moderator_name + response_data.user_id = _user_id + response_data.user_login = _user_login + response_data.user_name = _user_name + response_data.text = _text + response_data.status = _status + response_data.created_at = _created_at + response_data.resolved_at = _resolved_at + response_data.resolution_text = _resolution_text + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("moderator_login", null) != null: + result.moderator_login = d["moderator_login"] + if d.get("moderator_name", null) != null: + result.moderator_name = d["moderator_name"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("text", null) != null: + result.text = d["text"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("resolved_at", null) != null: + result.resolved_at = d["resolved_at"] + if d.get("resolution_text", null) != null: + result.resolution_text = d["resolution_text"] + return result + + + +## All optional parameters for TwitchAPI.resolve_unban_requests +## #/components/schemas/ResolveUnbanRequestsOpt +class Opt extends TwitchData: + + ## Message supplied by the unban request resolver. The message is limited to a maximum of 500 characters. + @export var resolution_text: String: + set(val): + resolution_text = val + track_data(&"resolution_text", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("resolution_text", null) != null: + result.resolution_text = d["resolution_text"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_resolve_unban_requests.gd.uid b/addons/twitcher/generated/twitch_resolve_unban_requests.gd.uid new file mode 100644 index 00000000..cabb72fd --- /dev/null +++ b/addons/twitcher/generated/twitch_resolve_unban_requests.gd.uid @@ -0,0 +1 @@ +uid://xdmooyrs5ejn diff --git a/addons/twitcher/generated/twitch_search_categories.gd b/addons/twitcher/generated/twitch_search_categories.gd new file mode 100644 index 00000000..f9635442 --- /dev/null +++ b/addons/twitcher/generated/twitch_search_categories.gd @@ -0,0 +1,144 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSearchCategories + + + +## +## #/components/schemas/SearchCategoriesResponse +class Response extends TwitchData: + + ## The list of games or categories that match the query. The list is empty if there are no matches. + @export var data: Array[TwitchCategory]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCategory]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCategory.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/SearchCategoriesResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.search_categories +## #/components/schemas/SearchCategoriesOpt +class Opt extends TwitchData: + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_search_categories.gd.uid b/addons/twitcher/generated/twitch_search_categories.gd.uid new file mode 100644 index 00000000..c6360698 --- /dev/null +++ b/addons/twitcher/generated/twitch_search_categories.gd.uid @@ -0,0 +1 @@ +uid://c5ou5278ciklb diff --git a/addons/twitcher/generated/twitch_search_channels.gd b/addons/twitcher/generated/twitch_search_channels.gd new file mode 100644 index 00000000..3e6e55b3 --- /dev/null +++ b/addons/twitcher/generated/twitch_search_channels.gd @@ -0,0 +1,152 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSearchChannels + + + +## +## #/components/schemas/SearchChannelsResponse +class Response extends TwitchData: + + ## The list of channels that match the query. The list is empty if there are no matches. + @export var data: Array[TwitchChannel]: + set(val): + data = val + track_data(&"data", val) + + ## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var pagination: ResponsePagination: + set(val): + pagination = val + track_data(&"pagination", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChannel]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChannel.from_json(value)) + if d.get("pagination", null) != null: + result.pagination = ResponsePagination.from_json(d["pagination"]) + return result + + + + func _has_pagination() -> bool: + if pagination == null: return false + if pagination.cursor == null || pagination.cursor == "": return false + return true + + var _next_page: Callable + var _cur_iter: int = 0 + + + func next_page() -> Response: + var response: Response = await _next_page.call() + _cur_iter = 0 + _next_page = response._next_page + data = response.data + pagination = response.pagination + + return response + + + func _iter_init(iter: Array) -> bool: + if data.is_empty(): return false + iter[0] = data[0] + _cur_iter = 1 + return true + + + func _iter_next(iter: Array) -> bool: + if data.size() > _cur_iter: + iter[0] = data[_cur_iter] + _cur_iter += 1 + elif not _has_pagination(): + return false + return true + + + func _iter_get(iter: Variant) -> Variant: + if data.size() - 1 == _cur_iter && _has_pagination(): + await next_page() + return iter + + +## Contains the information used to page through the list of results. The object is empty if there are no more pages left to page through.[Read More](https://dev.twitch.tv/docs/api/guide#pagination) +## #/components/schemas/SearchChannelsResponse/Pagination +class ResponsePagination extends TwitchData: + + ## The cursor used to get the next page of results. Use the cursor to set the request’s _after_ query parameter. + @export var cursor: String: + set(val): + cursor = val + track_data(&"cursor", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create() -> ResponsePagination: + var response_pagination: ResponsePagination = ResponsePagination.new() + return response_pagination + + + static func from_json(d: Dictionary) -> ResponsePagination: + var result: ResponsePagination = ResponsePagination.new() + if d.get("cursor", null) != null: + result.cursor = d["cursor"] + return result + + + +## All optional parameters for TwitchAPI.search_channels +## #/components/schemas/SearchChannelsOpt +class Opt extends TwitchData: + + ## A Boolean value that determines whether the response includes only channels that are currently streaming live. Set to **true** to get only channels that are streaming live; otherwise, **false** to get live and offline channels. The default is **false**. + @export var live_only: bool: + set(val): + live_only = val + track_data(&"live_only", val) + + ## The maximum number of items to return per page in the response. The minimum page size is 1 item per page and the maximum is 100 items per page. The default is 20. + @export var first: int: + set(val): + first = val + track_data(&"first", val) + + ## The cursor used to get the next page of results. The **Pagination** object in the response contains the cursor’s value. [Read More](https://dev.twitch.tv/docs/api/guide#pagination) + @export var after: String: + set(val): + after = val + track_data(&"after", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("live_only", null) != null: + result.live_only = d["live_only"] + if d.get("first", null) != null: + result.first = d["first"] + if d.get("after", null) != null: + result.after = d["after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_search_channels.gd.uid b/addons/twitcher/generated/twitch_search_channels.gd.uid new file mode 100644 index 00000000..b45529e4 --- /dev/null +++ b/addons/twitcher/generated/twitch_search_channels.gd.uid @@ -0,0 +1 @@ +uid://cm6j0tlaigxx5 diff --git a/addons/twitcher/generated/twitch_send_chat_announcement.gd b/addons/twitcher/generated/twitch_send_chat_announcement.gd new file mode 100644 index 00000000..d159a8bb --- /dev/null +++ b/addons/twitcher/generated/twitch_send_chat_announcement.gd @@ -0,0 +1,50 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSendChatAnnouncement + + + +## +## #/components/schemas/SendChatAnnouncementBody +class Body extends TwitchData: + + ## The announcement to make in the broadcaster’s chat room. Announcements are limited to a maximum of 500 characters; announcements longer than 500 characters are truncated. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + ## The color used to highlight the announcement. Possible case-sensitive values are: + ## + ## * blue + ## * green + ## * orange + ## * purple + ## * primary (default) + ## + ## If `color` is set to _primary_ or is not set, the channel’s accent color is used to highlight the announcement (see **Profile Accent Color** under [profile settings](https://www.twitch.tv/settings/profile), **Channel and Videos**, and **Brand**). + @export var color: String: + set(val): + color = val + track_data(&"color", val) + + + + ## Constructor with all required fields. + static func create(_message: String) -> Body: + var body: Body = Body.new() + body.message = _message + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("message", null) != null: + result.message = d["message"] + if d.get("color", null) != null: + result.color = d["color"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_send_chat_announcement.gd.uid b/addons/twitcher/generated/twitch_send_chat_announcement.gd.uid new file mode 100644 index 00000000..c4308c2f --- /dev/null +++ b/addons/twitcher/generated/twitch_send_chat_announcement.gd.uid @@ -0,0 +1 @@ +uid://bfen51gxc77ou diff --git a/addons/twitcher/generated/twitch_send_chat_message.gd b/addons/twitcher/generated/twitch_send_chat_message.gd new file mode 100644 index 00000000..ed026cd6 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_chat_message.gd @@ -0,0 +1,180 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSendChatMessage + + + +## +## #/components/schemas/SendChatMessageBody +class Body extends TwitchData: + + ## The ID of the broadcaster whose chat room the message will be sent to. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The ID of the user sending the message. This ID must match the user ID in the user access token. + @export var sender_id: String: + set(val): + sender_id = val + track_data(&"sender_id", val) + + ## The message to send. The message is limited to a maximum of 500 characters. Chat messages can also include emoticons. To include emoticons, use the name of the emote. The names are case sensitive. Don’t include colons around the name (e.g., :bleedPurple:). If Twitch recognizes the name, Twitch converts the name to the emote before writing the chat message to the chat room + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + ## The ID of the chat message being replied to. + @export var reply_parent_message_id: String: + set(val): + reply_parent_message_id = val + track_data(&"reply_parent_message_id", val) + + ## **NOTE:** This parameter can only be set when utilizing an App Access Token. It cannot be specified when a User Access Token is used, and will instead result in an HTTP 400 error. + ## + ## Determines if the chat message is sent only to the source channel (defined by _broadcaster\_id_) during a shared chat session. This has no effect if the message is sent during a shared chat session. + ## + ## If this parameter is not set, the default value when using an App Access Token is `false`. On May 19, 2025 the default value for this parameter will be updated to `true`, and chat messages sent using an App Access Token will only be shared with the source channel by default. If you prefer to send a chat message to both channels in a shared chat session, make sure this parameter is explicitly set to `false` in your API request before May 19. + @export var for_source_only: bool: + set(val): + for_source_only = val + track_data(&"for_source_only", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _sender_id: String, _message: String) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.sender_id = _sender_id + body.message = _message + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("sender_id", null) != null: + result.sender_id = d["sender_id"] + if d.get("message", null) != null: + result.message = d["message"] + if d.get("reply_parent_message_id", null) != null: + result.reply_parent_message_id = d["reply_parent_message_id"] + if d.get("for_source_only", null) != null: + result.for_source_only = d["for_source_only"] + return result + + + +## +## #/components/schemas/SendChatMessageResponse +class Response extends TwitchData: + + ## + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## +## #/components/schemas/SendChatMessageResponse/Data +class ResponseData extends TwitchData: + + ## The message id for the message that was sent. + @export var message_id: String: + set(val): + message_id = val + track_data(&"message_id", val) + + ## If the message passed all checks and was sent. + @export var is_sent: bool: + set(val): + is_sent = val + track_data(&"is_sent", val) + + ## The reason the message was dropped, if any. + @export var drop_reason: ResponseDropReason: + set(val): + drop_reason = val + track_data(&"drop_reason", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_message_id: String, _is_sent: bool) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.message_id = _message_id + response_data.is_sent = _is_sent + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("message_id", null) != null: + result.message_id = d["message_id"] + if d.get("is_sent", null) != null: + result.is_sent = d["is_sent"] + if d.get("drop_reason", null) != null: + result.drop_reason = ResponseDropReason.from_json(d["drop_reason"]) + return result + + + +## The reason the message was dropped, if any. +## #/components/schemas/SendChatMessageResponse/Data/DropReason +class ResponseDropReason extends TwitchData: + + ## Code for why the message was dropped. + @export var code: String: + set(val): + code = val + track_data(&"code", val) + + ## Message for why the message was dropped. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_code: String, _message: String) -> ResponseDropReason: + var response_drop_reason: ResponseDropReason = ResponseDropReason.new() + response_drop_reason.code = _code + response_drop_reason.message = _message + return response_drop_reason + + + static func from_json(d: Dictionary) -> ResponseDropReason: + var result: ResponseDropReason = ResponseDropReason.new() + if d.get("code", null) != null: + result.code = d["code"] + if d.get("message", null) != null: + result.message = d["message"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_send_chat_message.gd.uid b/addons/twitcher/generated/twitch_send_chat_message.gd.uid new file mode 100644 index 00000000..b18e7bd7 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_chat_message.gd.uid @@ -0,0 +1 @@ +uid://bwk0378nocfl2 diff --git a/addons/twitcher/generated/twitch_send_extension_chat_message.gd b/addons/twitcher/generated/twitch_send_extension_chat_message.gd new file mode 100644 index 00000000..99b05af7 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_extension_chat_message.gd @@ -0,0 +1,52 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSendExtensionChatMessage + + + +## +## #/components/schemas/SendExtensionChatMessageBody +class Body extends TwitchData: + + ## The message. The message may contain a maximum of 280 characters. + @export var text: String: + set(val): + text = val + track_data(&"text", val) + + ## The ID of the extension that’s sending the chat message. + @export var extension_id: String: + set(val): + extension_id = val + track_data(&"extension_id", val) + + ## The extension’s version number. + @export var extension_version: String: + set(val): + extension_version = val + track_data(&"extension_version", val) + + + + ## Constructor with all required fields. + static func create(_text: String, _extension_id: String, _extension_version: String) -> Body: + var body: Body = Body.new() + body.text = _text + body.extension_id = _extension_id + body.extension_version = _extension_version + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("text", null) != null: + result.text = d["text"] + if d.get("extension_id", null) != null: + result.extension_id = d["extension_id"] + if d.get("extension_version", null) != null: + result.extension_version = d["extension_version"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_send_extension_chat_message.gd.uid b/addons/twitcher/generated/twitch_send_extension_chat_message.gd.uid new file mode 100644 index 00000000..3569ce19 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_extension_chat_message.gd.uid @@ -0,0 +1 @@ +uid://bgldr1v6lh3mx diff --git a/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd b/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd new file mode 100644 index 00000000..4e9980f4 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd @@ -0,0 +1,67 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSendExtensionPubSubMessage + + + +## +## #/components/schemas/SendExtensionPubSubMessageBody +class Body extends TwitchData: + + ## The target of the message. Possible values are: + ## + ## * broadcast + ## * global + ## * whisper- + ## + ## If `is_global_broadcast` is **true**, you must set this field to global. The broadcast and global values are mutually exclusive; specify only one of them. + @export var target: Array[String]: + set(val): + target = val + track_data(&"target", val) + + ## The ID of the broadcaster to send the message to. Don’t include this field if `is_global_broadcast` is set to **true**. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## A Boolean value that determines whether the message should be sent to all channels where your extension is active. Set to **true** if the message should be sent to all channels. The default is **false**. + @export var is_global_broadcast: bool: + set(val): + is_global_broadcast = val + track_data(&"is_global_broadcast", val) + + ## The message to send. The message can be a plain-text string or a string-encoded JSON object. The message is limited to a maximum of 5 KB. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + + + ## Constructor with all required fields. + static func create(_target: Array[String], _broadcaster_id: String, _message: String) -> Body: + var body: Body = Body.new() + body.target = _target + body.broadcaster_id = _broadcaster_id + body.message = _message + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("target", null) != null: + for value in d["target"]: + result.target.append(value) + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("is_global_broadcast", null) != null: + result.is_global_broadcast = d["is_global_broadcast"] + if d.get("message", null) != null: + result.message = d["message"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd.uid b/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd.uid new file mode 100644 index 00000000..da14ea33 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_extension_pub_sub_message.gd.uid @@ -0,0 +1 @@ +uid://ospk2kwra5he diff --git a/addons/twitcher/generated/twitch_send_whisper.gd b/addons/twitcher/generated/twitch_send_whisper.gd new file mode 100644 index 00000000..49634ef1 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_whisper.gd @@ -0,0 +1,41 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSendWhisper + + + +## +## #/components/schemas/SendWhisperBody +class Body extends TwitchData: + + ## The whisper message to send. The message must not be empty. + ## + ## The maximum message lengths are: + ## + ## * 500 characters if the user you're sending the message to hasn't whispered you before. + ## * 10,000 characters if the user you're sending the message to has whispered you before. + ## + ## Messages that exceed the maximum length are truncated. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + + + ## Constructor with all required fields. + static func create(_message: String) -> Body: + var body: Body = Body.new() + body.message = _message + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("message", null) != null: + result.message = d["message"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_send_whisper.gd.uid b/addons/twitcher/generated/twitch_send_whisper.gd.uid new file mode 100644 index 00000000..338baa38 --- /dev/null +++ b/addons/twitcher/generated/twitch_send_whisper.gd.uid @@ -0,0 +1 @@ +uid://c73ksa7b5f0fl diff --git a/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd b/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd new file mode 100644 index 00000000..d80fe71e --- /dev/null +++ b/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd @@ -0,0 +1,71 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSetExtensionConfigurationSegment + + + +## +## #/components/schemas/SetExtensionConfigurationSegmentBody +class Body extends TwitchData: + + ## The ID of the extension to update. + @export var extension_id: String: + set(val): + extension_id = val + track_data(&"extension_id", val) + + ## The configuration segment to update. Possible case-sensitive values are: + ## + ## * broadcaster + ## * developer + ## * global + @export var segment: String: + set(val): + segment = val + track_data(&"segment", val) + + ## The ID of the broadcaster that installed the extension. Include this field only if the `segment` is set to developer or broadcaster. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The contents of the segment. This string may be a plain-text string or a string-encoded JSON object. + @export var content: String: + set(val): + content = val + track_data(&"content", val) + + ## The version number that identifies this definition of the segment’s data. If not specified, the latest definition is updated. + @export var version: String: + set(val): + version = val + track_data(&"version", val) + + + + ## Constructor with all required fields. + static func create(_extension_id: String, _segment: String) -> Body: + var body: Body = Body.new() + body.extension_id = _extension_id + body.segment = _segment + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("extension_id", null) != null: + result.extension_id = d["extension_id"] + if d.get("segment", null) != null: + result.segment = d["segment"] + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("content", null) != null: + result.content = d["content"] + if d.get("version", null) != null: + result.version = d["version"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd.uid b/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd.uid new file mode 100644 index 00000000..694c16ba --- /dev/null +++ b/addons/twitcher/generated/twitch_set_extension_configuration_segment.gd.uid @@ -0,0 +1 @@ +uid://bb8hyabfsyyag diff --git a/addons/twitcher/generated/twitch_set_extension_required_configuration.gd b/addons/twitcher/generated/twitch_set_extension_required_configuration.gd new file mode 100644 index 00000000..343a1832 --- /dev/null +++ b/addons/twitcher/generated/twitch_set_extension_required_configuration.gd @@ -0,0 +1,52 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSetExtensionRequiredConfiguration + + + +## +## #/components/schemas/SetExtensionRequiredConfigurationBody +class Body extends TwitchData: + + ## The ID of the extension to update. + @export var extension_id: String: + set(val): + extension_id = val + track_data(&"extension_id", val) + + ## The version of the extension to update. + @export var extension_version: String: + set(val): + extension_version = val + track_data(&"extension_version", val) + + ## The required\_configuration string to use with the extension. + @export var required_configuration: String: + set(val): + required_configuration = val + track_data(&"required_configuration", val) + + + + ## Constructor with all required fields. + static func create(_extension_id: String, _extension_version: String, _required_configuration: String) -> Body: + var body: Body = Body.new() + body.extension_id = _extension_id + body.extension_version = _extension_version + body.required_configuration = _required_configuration + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("extension_id", null) != null: + result.extension_id = d["extension_id"] + if d.get("extension_version", null) != null: + result.extension_version = d["extension_version"] + if d.get("required_configuration", null) != null: + result.required_configuration = d["required_configuration"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_set_extension_required_configuration.gd.uid b/addons/twitcher/generated/twitch_set_extension_required_configuration.gd.uid new file mode 100644 index 00000000..d533d65f --- /dev/null +++ b/addons/twitcher/generated/twitch_set_extension_required_configuration.gd.uid @@ -0,0 +1 @@ +uid://b1x0l12h5kmu5 diff --git a/addons/twitcher/generated/twitch_snooze_next_ad.gd b/addons/twitcher/generated/twitch_snooze_next_ad.gd new file mode 100644 index 00000000..f06447e3 --- /dev/null +++ b/addons/twitcher/generated/twitch_snooze_next_ad.gd @@ -0,0 +1,80 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchSnoozeNextAd + + + +## +## #/components/schemas/SnoozeNextAdResponse +class Response extends TwitchData: + + ## A list that contains information about the channel’s snoozes and next upcoming ad after successfully snoozing. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains information about the channel’s snoozes and next upcoming ad after successfully snoozing. +## #/components/schemas/SnoozeNextAdResponse/Data +class ResponseData extends TwitchData: + + ## The number of snoozes available for the broadcaster. + @export var snooze_count: int: + set(val): + snooze_count = val + track_data(&"snooze_count", val) + + ## The UTC timestamp when the broadcaster will gain an additional snooze, in RFC3339 format. + @export var snooze_refresh_at: String: + set(val): + snooze_refresh_at = val + track_data(&"snooze_refresh_at", val) + + ## The UTC timestamp of the broadcaster’s next scheduled ad, in RFC3339 format. + @export var next_ad_at: String: + set(val): + next_ad_at = val + track_data(&"next_ad_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_snooze_count: int, _snooze_refresh_at: String, _next_ad_at: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.snooze_count = _snooze_count + response_data.snooze_refresh_at = _snooze_refresh_at + response_data.next_ad_at = _next_ad_at + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("snooze_count", null) != null: + result.snooze_count = d["snooze_count"] + if d.get("snooze_refresh_at", null) != null: + result.snooze_refresh_at = d["snooze_refresh_at"] + if d.get("next_ad_at", null) != null: + result.next_ad_at = d["next_ad_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_snooze_next_ad.gd.uid b/addons/twitcher/generated/twitch_snooze_next_ad.gd.uid new file mode 100644 index 00000000..c6482085 --- /dev/null +++ b/addons/twitcher/generated/twitch_snooze_next_ad.gd.uid @@ -0,0 +1 @@ +uid://dhw1xxk2dc24f diff --git a/addons/twitcher/generated/twitch_start_a_raid.gd b/addons/twitcher/generated/twitch_start_a_raid.gd new file mode 100644 index 00000000..40046d69 --- /dev/null +++ b/addons/twitcher/generated/twitch_start_a_raid.gd @@ -0,0 +1,41 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchStartARaid + + + +## All optional parameters for TwitchAPI.start_a_raid +## #/components/schemas/StartARaidOpt +class Opt extends TwitchData: + + ## The ID of the broadcaster that’s sending the raiding party. This ID must match the user ID in the user access token. + @export var from_broadcaster_id: String: + set(val): + from_broadcaster_id = val + track_data(&"from_broadcaster_id", val) + + ## The ID of the broadcaster to raid. + @export var to_broadcaster_id: String: + set(val): + to_broadcaster_id = val + track_data(&"to_broadcaster_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("from_broadcaster_id", null) != null: + result.from_broadcaster_id = d["from_broadcaster_id"] + if d.get("to_broadcaster_id", null) != null: + result.to_broadcaster_id = d["to_broadcaster_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_start_a_raid.gd.uid b/addons/twitcher/generated/twitch_start_a_raid.gd.uid new file mode 100644 index 00000000..73e36a79 --- /dev/null +++ b/addons/twitcher/generated/twitch_start_a_raid.gd.uid @@ -0,0 +1 @@ +uid://b2ypwr1s02sns diff --git a/addons/twitcher/generated/twitch_start_commercial.gd b/addons/twitcher/generated/twitch_start_commercial.gd new file mode 100644 index 00000000..ac585e32 --- /dev/null +++ b/addons/twitcher/generated/twitch_start_commercial.gd @@ -0,0 +1,116 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchStartCommercial + + + +## +## #/components/schemas/StartCommercialBody +class Body extends TwitchData: + + ## The ID of the partner or affiliate broadcaster that wants to run the commercial. This ID must match the user ID found in the OAuth token. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The length of the commercial to run, in seconds. Twitch tries to serve a commercial that’s the requested length, but it may be shorter or longer. The maximum length you should request is 180 seconds. + @export var length: int: + set(val): + length = val + track_data(&"length", val) + + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _length: int) -> Body: + var body: Body = Body.new() + body.broadcaster_id = _broadcaster_id + body.length = _length + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("length", null) != null: + result.length = d["length"] + return result + + + +## +## #/components/schemas/StartCommercialResponse +class Response extends TwitchData: + + ## An array that contains a single object with the status of your start commercial request. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## An array that contains a single object with the status of your start commercial request. +## #/components/schemas/StartCommercialResponse/Data +class ResponseData extends TwitchData: + + ## The length of the commercial you requested. If you request a commercial that’s longer than 180 seconds, the API uses 180 seconds. + @export var length: int: + set(val): + length = val + track_data(&"length", val) + + ## A message that indicates whether Twitch was able to serve an ad. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + ## The number of seconds you must wait before running another commercial. + @export var retry_after: int: + set(val): + retry_after = val + track_data(&"retry_after", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_length: int, _message: String, _retry_after: int) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.length = _length + response_data.message = _message + response_data.retry_after = _retry_after + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("length", null) != null: + result.length = d["length"] + if d.get("message", null) != null: + result.message = d["message"] + if d.get("retry_after", null) != null: + result.retry_after = d["retry_after"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_start_commercial.gd.uid b/addons/twitcher/generated/twitch_start_commercial.gd.uid new file mode 100644 index 00000000..9409be27 --- /dev/null +++ b/addons/twitcher/generated/twitch_start_commercial.gd.uid @@ -0,0 +1 @@ +uid://b07j06b3w1uyw diff --git a/addons/twitcher/generated/twitch_start_raid.gd b/addons/twitcher/generated/twitch_start_raid.gd new file mode 100644 index 00000000..9bd62c5b --- /dev/null +++ b/addons/twitcher/generated/twitch_start_raid.gd @@ -0,0 +1,71 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchStartRaid + + + +## +## #/components/schemas/StartRaidResponse +class Response extends TwitchData: + + ## A list that contains a single object with information about the pending raid. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains a single object with information about the pending raid. +## #/components/schemas/StartRaidResponse/Data +class ResponseData extends TwitchData: + + ## The UTC date and time, in RFC3339 format, of when the raid was requested. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## A Boolean value that indicates whether the channel being raided contains mature content. + @export var is_mature: bool: + set(val): + is_mature = val + track_data(&"is_mature", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_created_at: String, _is_mature: bool) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.created_at = _created_at + response_data.is_mature = _is_mature + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("is_mature", null) != null: + result.is_mature = d["is_mature"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_start_raid.gd.uid b/addons/twitcher/generated/twitch_start_raid.gd.uid new file mode 100644 index 00000000..876d0856 --- /dev/null +++ b/addons/twitcher/generated/twitch_start_raid.gd.uid @@ -0,0 +1 @@ +uid://crnrjqojt0t31 diff --git a/addons/twitcher/generated/twitch_stream.gd b/addons/twitcher/generated/twitch_stream.gd new file mode 100644 index 00000000..37f57b48 --- /dev/null +++ b/addons/twitcher/generated/twitch_stream.gd @@ -0,0 +1,163 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Stream +class_name TwitchStream + +## An ID that identifies the stream. You can use this ID later to look up the video on demand (VOD). +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The ID of the user that’s broadcasting the stream. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The ID of the category or game being played. +@export var game_id: String: + set(val): + game_id = val + track_data(&"game_id", val) + +## The ID of the category or game being played. +@export var game_name: String: + set(val): + game_name = val + track_data(&"game_name", val) + +## The type of stream. Possible values are: +## +## * live +## +## If an error occurs, this field is set to an empty string. +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The stream’s title. Is an empty string if not set. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The number of users watching the stream. +@export var viewer_count: int: + set(val): + viewer_count = val + track_data(&"viewer_count", val) + +## The UTC date and time (in RFC3339 format) of when the broadcast began. +@export var started_at: String: + set(val): + started_at = val + track_data(&"started_at", val) + +## The language that the stream uses. This is an ISO 639-1 two-letter language code or _other_ if the stream uses a language not in the list of [supported stream languages](https://help.twitch.tv/s/article/languages-on-twitch#streamlang). +@export var language: String: + set(val): + language = val + track_data(&"language", val) + +## A URL to an image of a frame from the last 5 minutes of the stream. Replace the width and height placeholders in the URL (`{width}x{height}`) with the size of the image you want, in pixels. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## **IMPORTANT** As of February 28, 2023, this field is deprecated and returns only an empty array. If you use this field, please update your code to use the `tags` field. +## +## The list of tags that apply to the stream. The list contains IDs only when the channel is steaming live. For a list of possible tags, see [List of All Tags](https://www.twitch.tv/directory/all/tags). The list doesn’t include Category Tags. +@export var tag_ids: Array[String]: + set(val): + tag_ids = val + track_data(&"tag_ids", val) + +## The tags applied to the stream. +@export var tags: Array[String]: + set(val): + tags = val + track_data(&"tags", val) + +## A Boolean value that indicates whether the stream is meant for mature audiences. +@export var is_mature: bool: + set(val): + is_mature = val + track_data(&"is_mature", val) + + + +## Constructor with all required fields. +static func create(_id: String, _user_id: String, _user_login: String, _user_name: String, _game_id: String, _game_name: String, _type: String, _title: String, _viewer_count: int, _started_at: String, _language: String, _thumbnail_url: String, _tag_ids: Array[String], _tags: Array[String], _is_mature: bool) -> TwitchStream: + var twitch_stream: TwitchStream = TwitchStream.new() + twitch_stream.id = _id + twitch_stream.user_id = _user_id + twitch_stream.user_login = _user_login + twitch_stream.user_name = _user_name + twitch_stream.game_id = _game_id + twitch_stream.game_name = _game_name + twitch_stream.type = _type + twitch_stream.title = _title + twitch_stream.viewer_count = _viewer_count + twitch_stream.started_at = _started_at + twitch_stream.language = _language + twitch_stream.thumbnail_url = _thumbnail_url + twitch_stream.tag_ids = _tag_ids + twitch_stream.tags = _tags + twitch_stream.is_mature = _is_mature + return twitch_stream + + +static func from_json(d: Dictionary) -> TwitchStream: + var result: TwitchStream = TwitchStream.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("game_id", null) != null: + result.game_id = d["game_id"] + if d.get("game_name", null) != null: + result.game_name = d["game_name"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("viewer_count", null) != null: + result.viewer_count = d["viewer_count"] + if d.get("started_at", null) != null: + result.started_at = d["started_at"] + if d.get("language", null) != null: + result.language = d["language"] + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("tag_ids", null) != null: + for value in d["tag_ids"]: + result.tag_ids.append(value) + if d.get("tags", null) != null: + for value in d["tags"]: + result.tags.append(value) + if d.get("is_mature", null) != null: + result.is_mature = d["is_mature"] + return result diff --git a/addons/twitcher/generated/twitch_stream.gd.uid b/addons/twitcher/generated/twitch_stream.gd.uid new file mode 100644 index 00000000..7c113493 --- /dev/null +++ b/addons/twitcher/generated/twitch_stream.gd.uid @@ -0,0 +1 @@ +uid://ti42wkq54dsq diff --git a/addons/twitcher/generated/twitch_stream_marker_created.gd b/addons/twitcher/generated/twitch_stream_marker_created.gd new file mode 100644 index 00000000..a6902d5f --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_marker_created.gd @@ -0,0 +1,56 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/StreamMarkerCreated +class_name TwitchStreamMarkerCreated + +## An ID that identifies this marker. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The UTC date and time (in RFC3339 format) of when the user created the marker. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The relative offset (in seconds) of the marker from the beginning of the stream. +@export var position_seconds: int: + set(val): + position_seconds = val + track_data(&"position_seconds", val) + +## A description that the user gave the marker to help them remember why they marked the location. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + + + +## Constructor with all required fields. +static func create(_id: String, _created_at: String, _position_seconds: int, _description: String) -> TwitchStreamMarkerCreated: + var twitch_stream_marker_created: TwitchStreamMarkerCreated = TwitchStreamMarkerCreated.new() + twitch_stream_marker_created.id = _id + twitch_stream_marker_created.created_at = _created_at + twitch_stream_marker_created.position_seconds = _position_seconds + twitch_stream_marker_created.description = _description + return twitch_stream_marker_created + + +static func from_json(d: Dictionary) -> TwitchStreamMarkerCreated: + var result: TwitchStreamMarkerCreated = TwitchStreamMarkerCreated.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("position_seconds", null) != null: + result.position_seconds = d["position_seconds"] + if d.get("description", null) != null: + result.description = d["description"] + return result diff --git a/addons/twitcher/generated/twitch_stream_marker_created.gd.uid b/addons/twitcher/generated/twitch_stream_marker_created.gd.uid new file mode 100644 index 00000000..1cec6c43 --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_marker_created.gd.uid @@ -0,0 +1 @@ +uid://c6psfioqifwww diff --git a/addons/twitcher/generated/twitch_stream_markers.gd b/addons/twitcher/generated/twitch_stream_markers.gd new file mode 100644 index 00000000..c1db3c96 --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_markers.gd @@ -0,0 +1,140 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/StreamMarkers +class_name TwitchStreamMarkers + +## The ID of the user that created the marker. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## A list of videos that contain markers. The list contains a single video. +@export var videos: Array[Variant]: + set(val): + videos = val + track_data(&"videos", val) + +## An ID that identifies this video. +@export var video_id: String: + set(val): + video_id = val + track_data(&"video_id", val) + +## The list of markers in this video. The list in ascending order by when the marker was created. +@export var markers: Array[Markers]: + set(val): + markers = val + track_data(&"markers", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_name: String, _user_login: String, _videos: Array[Variant], _video_id: String, _markers: Array[Markers]) -> TwitchStreamMarkers: + var twitch_stream_markers: TwitchStreamMarkers = TwitchStreamMarkers.new() + twitch_stream_markers.user_id = _user_id + twitch_stream_markers.user_name = _user_name + twitch_stream_markers.user_login = _user_login + twitch_stream_markers.videos = _videos + twitch_stream_markers.video_id = _video_id + twitch_stream_markers.markers = _markers + return twitch_stream_markers + + +static func from_json(d: Dictionary) -> TwitchStreamMarkers: + var result: TwitchStreamMarkers = TwitchStreamMarkers.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("videos", null) != null: + for value in d["videos"]: + result.videos.append(value) + if d.get("video_id", null) != null: + result.video_id = d["video_id"] + if d.get("markers", null) != null: + for value in d["markers"]: + result.markers.append(Markers.from_json(value)) + return result + + + +## The list of markers in this video. The list in ascending order by when the marker was created. +## #/components/schemas/StreamMarkers/Markers +class Markers extends TwitchData: + + ## An ID that identifies this marker. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The UTC date and time (in RFC3339 format) of when the user created the marker. + @export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + ## The description that the user gave the marker to help them remember why they marked the location. Is an empty string if the user didn’t provide one. + @export var description: String: + set(val): + description = val + track_data(&"description", val) + + ## The relative offset (in seconds) of the marker from the beginning of the stream. + @export var position_seconds: int: + set(val): + position_seconds = val + track_data(&"position_seconds", val) + + ## A URL that opens the video in Twitch Highlighter. + @export var url: String: + set(val): + url = val + track_data(&"url", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _created_at: String, _description: String, _position_seconds: int, _url: String) -> Markers: + var markers: Markers = Markers.new() + markers.id = _id + markers.created_at = _created_at + markers.description = _description + markers.position_seconds = _position_seconds + markers.url = _url + return markers + + + static func from_json(d: Dictionary) -> Markers: + var result: Markers = Markers.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("position_seconds", null) != null: + result.position_seconds = d["position_seconds"] + if d.get("url", null) != null: + result.url = d["url"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_stream_markers.gd.uid b/addons/twitcher/generated/twitch_stream_markers.gd.uid new file mode 100644 index 00000000..b69b0357 --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_markers.gd.uid @@ -0,0 +1 @@ +uid://cymemfsps1hl5 diff --git a/addons/twitcher/generated/twitch_stream_tag.gd b/addons/twitcher/generated/twitch_stream_tag.gd new file mode 100644 index 00000000..64de6b0d --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_tag.gd @@ -0,0 +1,56 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/StreamTag +class_name TwitchStreamTag + +## An ID that identifies this tag. +@export var tag_id: String: + set(val): + tag_id = val + track_data(&"tag_id", val) + +## A Boolean value that determines whether the tag is an automatic tag. An automatic tag is one that Twitch adds to the stream. Broadcasters may not add automatic tags to their channel. The value is **true** if the tag is an automatic tag; otherwise, **false**. +@export var is_auto: bool: + set(val): + is_auto = val + track_data(&"is_auto", val) + +## A dictionary that contains the localized names of the tag. The key is in the form, -. For example, en-us. The value is the localized name. +@export var localization_names: Dictionary: + set(val): + localization_names = val + track_data(&"localization_names", val) + +## A dictionary that contains the localized descriptions of the tag. The key is in the form, -. For example, en-us. The value is the localized description. +@export var localization_descriptions: Dictionary: + set(val): + localization_descriptions = val + track_data(&"localization_descriptions", val) + + + +## Constructor with all required fields. +static func create(_tag_id: String, _is_auto: bool, _localization_names: Dictionary, _localization_descriptions: Dictionary) -> TwitchStreamTag: + var twitch_stream_tag: TwitchStreamTag = TwitchStreamTag.new() + twitch_stream_tag.tag_id = _tag_id + twitch_stream_tag.is_auto = _is_auto + twitch_stream_tag.localization_names = _localization_names + twitch_stream_tag.localization_descriptions = _localization_descriptions + return twitch_stream_tag + + +static func from_json(d: Dictionary) -> TwitchStreamTag: + var result: TwitchStreamTag = TwitchStreamTag.new() + if d.get("tag_id", null) != null: + result.tag_id = d["tag_id"] + if d.get("is_auto", null) != null: + result.is_auto = d["is_auto"] + if d.get("localization_names", null) != null: + result.localization_names = d["localization_names"] + if d.get("localization_descriptions", null) != null: + result.localization_descriptions = d["localization_descriptions"] + return result diff --git a/addons/twitcher/generated/twitch_stream_tag.gd.uid b/addons/twitcher/generated/twitch_stream_tag.gd.uid new file mode 100644 index 00000000..825f494f --- /dev/null +++ b/addons/twitcher/generated/twitch_stream_tag.gd.uid @@ -0,0 +1 @@ +uid://d0apvlp414ivb diff --git a/addons/twitcher/generated/twitch_team.gd b/addons/twitcher/generated/twitch_team.gd new file mode 100644 index 00000000..3a497948 --- /dev/null +++ b/addons/twitcher/generated/twitch_team.gd @@ -0,0 +1,157 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Team +class_name TwitchTeam + +## The list of team members. +@export var users: Array[Users]: + set(val): + users = val + track_data(&"users", val) + +## A URL to the team’s background image. +@export var background_image_url: String: + set(val): + background_image_url = val + track_data(&"background_image_url", val) + +## A URL to the team’s banner. +@export var banner: String: + set(val): + banner = val + track_data(&"banner", val) + +## The UTC date and time (in RFC3339 format) of when the team was created. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The UTC date and time (in RFC3339 format) of the last time the team was updated. +@export var updated_at: String: + set(val): + updated_at = val + track_data(&"updated_at", val) + +## The team’s description. The description may contain formatting such as Markdown, HTML, newline (\\n) characters, etc. +@export var info: String: + set(val): + info = val + track_data(&"info", val) + +## A URL to a thumbnail image of the team’s logo. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## The team’s name. +@export var team_name: String: + set(val): + team_name = val + track_data(&"team_name", val) + +## The team’s display name. +@export var team_display_name: String: + set(val): + team_display_name = val + track_data(&"team_display_name", val) + +## An ID that identifies the team. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + + + +## Constructor with all required fields. +static func create(_users: Array[Users], _background_image_url: String, _banner: String, _created_at: String, _updated_at: String, _info: String, _thumbnail_url: String, _team_name: String, _team_display_name: String, _id: String) -> TwitchTeam: + var twitch_team: TwitchTeam = TwitchTeam.new() + twitch_team.users = _users + twitch_team.background_image_url = _background_image_url + twitch_team.banner = _banner + twitch_team.created_at = _created_at + twitch_team.updated_at = _updated_at + twitch_team.info = _info + twitch_team.thumbnail_url = _thumbnail_url + twitch_team.team_name = _team_name + twitch_team.team_display_name = _team_display_name + twitch_team.id = _id + return twitch_team + + +static func from_json(d: Dictionary) -> TwitchTeam: + var result: TwitchTeam = TwitchTeam.new() + if d.get("users", null) != null: + for value in d["users"]: + result.users.append(Users.from_json(value)) + if d.get("background_image_url", null) != null: + result.background_image_url = d["background_image_url"] + if d.get("banner", null) != null: + result.banner = d["banner"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("updated_at", null) != null: + result.updated_at = d["updated_at"] + if d.get("info", null) != null: + result.info = d["info"] + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("team_name", null) != null: + result.team_name = d["team_name"] + if d.get("team_display_name", null) != null: + result.team_display_name = d["team_display_name"] + if d.get("id", null) != null: + result.id = d["id"] + return result + + + +## The list of team members. +## #/components/schemas/Team/Users +class Users extends TwitchData: + + ## An ID that identifies the team member. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The team member’s login name. + @export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + ## The team member’s display name. + @export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String, _user_login: String, _user_name: String) -> Users: + var users: Users = Users.new() + users.user_id = _user_id + users.user_login = _user_login + users.user_name = _user_name + return users + + + static func from_json(d: Dictionary) -> Users: + var result: Users = Users.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_team.gd.uid b/addons/twitcher/generated/twitch_team.gd.uid new file mode 100644 index 00000000..dbef957d --- /dev/null +++ b/addons/twitcher/generated/twitch_team.gd.uid @@ -0,0 +1 @@ +uid://bfc8teem0sjwd diff --git a/addons/twitcher/generated/twitch_update_auto_mod_settings.gd b/addons/twitcher/generated/twitch_update_auto_mod_settings.gd new file mode 100644 index 00000000..ed433273 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_auto_mod_settings.gd @@ -0,0 +1,125 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateAutoModSettings + + + +## +## #/components/schemas/UpdateAutoModSettingsBody +class Body extends TwitchData: + + ## The Automod level for hostility involving aggression. + @export var aggression: int: + set(val): + aggression = val + track_data(&"aggression", val) + + ## The Automod level for hostility involving name calling or insults. + @export var bullying: int: + set(val): + bullying = val + track_data(&"bullying", val) + + ## The Automod level for discrimination against disability. + @export var disability: int: + set(val): + disability = val + track_data(&"disability", val) + + ## The Automod level for discrimination against women. + @export var misogyny: int: + set(val): + misogyny = val + track_data(&"misogyny", val) + + ## The default AutoMod level for the broadcaster. + @export var overall_level: int: + set(val): + overall_level = val + track_data(&"overall_level", val) + + ## The Automod level for racial discrimination. + @export var race_ethnicity_or_religion: int: + set(val): + race_ethnicity_or_religion = val + track_data(&"race_ethnicity_or_religion", val) + + ## The Automod level for sexual content. + @export var sex_based_terms: int: + set(val): + sex_based_terms = val + track_data(&"sex_based_terms", val) + + ## The AutoMod level for discrimination based on sexuality, sex, or gender. + @export var sexuality_sex_or_gender: int: + set(val): + sexuality_sex_or_gender = val + track_data(&"sexuality_sex_or_gender", val) + + ## The Automod level for profanity. + @export var swearing: int: + set(val): + swearing = val + track_data(&"swearing", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("aggression", null) != null: + result.aggression = d["aggression"] + if d.get("bullying", null) != null: + result.bullying = d["bullying"] + if d.get("disability", null) != null: + result.disability = d["disability"] + if d.get("misogyny", null) != null: + result.misogyny = d["misogyny"] + if d.get("overall_level", null) != null: + result.overall_level = d["overall_level"] + if d.get("race_ethnicity_or_religion", null) != null: + result.race_ethnicity_or_religion = d["race_ethnicity_or_religion"] + if d.get("sex_based_terms", null) != null: + result.sex_based_terms = d["sex_based_terms"] + if d.get("sexuality_sex_or_gender", null) != null: + result.sexuality_sex_or_gender = d["sexuality_sex_or_gender"] + if d.get("swearing", null) != null: + result.swearing = d["swearing"] + return result + + + +## +## #/components/schemas/UpdateAutoModSettingsResponse +class Response extends TwitchData: + + ## The list of AutoMod settings. The list contains a single object that contains all the AutoMod settings. + @export var data: Array[TwitchAutoModSettings]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchAutoModSettings]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchAutoModSettings.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_auto_mod_settings.gd.uid b/addons/twitcher/generated/twitch_update_auto_mod_settings.gd.uid new file mode 100644 index 00000000..d029b9da --- /dev/null +++ b/addons/twitcher/generated/twitch_update_auto_mod_settings.gd.uid @@ -0,0 +1 @@ +uid://bia0nlvntfcg4 diff --git a/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd b/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd new file mode 100644 index 00000000..89594a49 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd @@ -0,0 +1,70 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateChannelGuestStarSettings + + + +## +## #/components/schemas/UpdateChannelGuestStarSettingsBody +class Body extends TwitchData: + + ## Flag determining if Guest Star moderators have access to control whether a guest is live once assigned to a slot. + @export var is_moderator_send_live_enabled: bool: + set(val): + is_moderator_send_live_enabled = val + track_data(&"is_moderator_send_live_enabled", val) + + ## Number of slots the Guest Star call interface will allow the host to add to a call. Required to be between 1 and 6. + @export var slot_count: int: + set(val): + slot_count = val + track_data(&"slot_count", val) + + ## Flag determining if Browser Sources subscribed to sessions on this channel should output audio + @export var is_browser_source_audio_enabled: bool: + set(val): + is_browser_source_audio_enabled = val + track_data(&"is_browser_source_audio_enabled", val) + + ## This setting determines how the guests within a session should be laid out within the browser source. Can be one of the following values: + ## + ## * `TILED_LAYOUT`: All live guests are tiled within the browser source with the same size. + ## * `SCREENSHARE_LAYOUT`: All live guests are tiled within the browser source with the same size. If there is an active screen share, it is sized larger than the other guests. + ## * `HORIZONTAL_LAYOUT`: All live guests are arranged in a horizontal bar within the browser source + ## * `VERTICAL_LAYOUT`: All live guests are arranged in a vertical bar within the browser source + @export var group_layout: String: + set(val): + group_layout = val + track_data(&"group_layout", val) + + ## Flag determining if Guest Star should regenerate the auth token associated with the channel’s browser sources. Providing a true value for this will immediately invalidate all browser sources previously configured in your streaming software. + @export var regenerate_browser_sources: bool: + set(val): + regenerate_browser_sources = val + track_data(&"regenerate_browser_sources", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("is_moderator_send_live_enabled", null) != null: + result.is_moderator_send_live_enabled = d["is_moderator_send_live_enabled"] + if d.get("slot_count", null) != null: + result.slot_count = d["slot_count"] + if d.get("is_browser_source_audio_enabled", null) != null: + result.is_browser_source_audio_enabled = d["is_browser_source_audio_enabled"] + if d.get("group_layout", null) != null: + result.group_layout = d["group_layout"] + if d.get("regenerate_browser_sources", null) != null: + result.regenerate_browser_sources = d["regenerate_browser_sources"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd.uid b/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd.uid new file mode 100644 index 00000000..b085f2a8 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_guest_star_settings.gd.uid @@ -0,0 +1 @@ +uid://cymuyecuhv371 diff --git a/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd b/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd new file mode 100644 index 00000000..3b5c9d89 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd @@ -0,0 +1,57 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateChannelStreamSchedule + + + +## All optional parameters for TwitchAPI.update_channel_stream_schedule +## #/components/schemas/UpdateChannelStreamScheduleOpt +class Opt extends TwitchData: + + ## A Boolean value that indicates whether the broadcaster has scheduled a vacation. Set to **true** to enable Vacation Mode and add vacation dates, or **false** to cancel a previously scheduled vacation. + @export var is_vacation_enabled: bool: + set(val): + is_vacation_enabled = val + track_data(&"is_vacation_enabled", val) + + ## The UTC date and time of when the broadcaster’s vacation starts. Specify the date and time in RFC3339 format (for example, 2021-05-16T00:00:00Z). Required if _is\_vacation\_enabled_ is **true**. + @export var vacation_start_time: String: + set(val): + vacation_start_time = val + track_data(&"vacation_start_time", val) + + ## The UTC date and time of when the broadcaster’s vacation ends. Specify the date and time in RFC3339 format (for example, 2021-05-30T23:59:59Z). Required if _is\_vacation\_enabled_ is **true**. + @export var vacation_end_time: String: + set(val): + vacation_end_time = val + track_data(&"vacation_end_time", val) + + ## The time zone that the broadcaster broadcasts from. Specify the time zone using [IANA time zone database](https://www.iana.org/time-zones) format (for example, America/New\_York). Required if _is\_vacation\_enabled_ is **true**. + @export var timezone: String: + set(val): + timezone = val + track_data(&"timezone", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("is_vacation_enabled", null) != null: + result.is_vacation_enabled = d["is_vacation_enabled"] + if d.get("vacation_start_time", null) != null: + result.vacation_start_time = d["vacation_start_time"] + if d.get("vacation_end_time", null) != null: + result.vacation_end_time = d["vacation_end_time"] + if d.get("timezone", null) != null: + result.timezone = d["timezone"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd.uid b/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd.uid new file mode 100644 index 00000000..bc119f07 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_stream_schedule.gd.uid @@ -0,0 +1 @@ +uid://bw0okld3eve8l diff --git a/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd b/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd new file mode 100644 index 00000000..fc568232 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd @@ -0,0 +1,204 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateChannelStreamScheduleSegment + + + +## +## #/components/schemas/UpdateChannelStreamScheduleSegmentBody +class Body extends TwitchData: + + ## The date and time that the broadcast segment starts. Specify the date and time in RFC3339 format (for example, 2022-08-02T06:00:00Z). + ## + ## **NOTE**: Only partners and affiliates may update a broadcast’s start time and only for non-recurring segments. + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## The length of time, in minutes, that the broadcast is scheduled to run. The duration must be in the range 30 through 1380 (23 hours). + @export var duration: String: + set(val): + duration = val + track_data(&"duration", val) + + ## The ID of the category that best represents the broadcast’s content. To get the category ID, use the [Search Categories](https://dev.twitch.tv/docs/api/reference#search-categories) endpoint. + @export var category_id: String: + set(val): + category_id = val + track_data(&"category_id", val) + + ## The broadcast’s title. The title may contain a maximum of 140 characters. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## A Boolean value that indicates whether the broadcast is canceled. Set to **true** to cancel the segment. + ## + ## **NOTE**: For recurring segments, the API cancels the first segment after the current UTC date and time and not the specified segment (unless the specified segment is the next segment after the current UTC date and time). + @export var is_canceled: bool: + set(val): + is_canceled = val + track_data(&"is_canceled", val) + + ## The time zone where the broadcast takes place. Specify the time zone using [IANA time zone database](https://www.iana.org/time-zones) format (for example, America/New\_York). + @export var timezone: String: + set(val): + timezone = val + track_data(&"timezone", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("category_id", null) != null: + result.category_id = d["category_id"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("is_canceled", null) != null: + result.is_canceled = d["is_canceled"] + if d.get("timezone", null) != null: + result.timezone = d["timezone"] + return result + + + +## +## #/components/schemas/UpdateChannelStreamScheduleSegmentResponse +class Response extends TwitchData: + + ## The broadcaster’s streaming scheduled. + @export var data: ResponseData: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: ResponseData) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + result.data = ResponseData.from_json(d["data"]) + return result + + + +## The broadcaster’s streaming scheduled. +## #/components/schemas/UpdateChannelStreamScheduleSegmentResponse/Data +class ResponseData extends TwitchData: + + ## A list that contains the single broadcast segment that you updated. + @export var segments: Array[TwitchChannelStreamScheduleSegment]: + set(val): + segments = val + track_data(&"segments", val) + + ## The ID of the broadcaster that owns the broadcast schedule. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The broadcaster’s display name. + @export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + + ## The broadcaster’s login name. + @export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + + ## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. + @export var vacation: ResponseVacation: + set(val): + vacation = val + track_data(&"vacation", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_segments: Array[TwitchChannelStreamScheduleSegment], _broadcaster_id: String, _broadcaster_name: String, _broadcaster_login: String, _vacation: ResponseVacation) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.segments = _segments + response_data.broadcaster_id = _broadcaster_id + response_data.broadcaster_name = _broadcaster_name + response_data.broadcaster_login = _broadcaster_login + response_data.vacation = _vacation + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("segments", null) != null: + for value in d["segments"]: + result.segments.append(TwitchChannelStreamScheduleSegment.from_json(value)) + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("vacation", null) != null: + result.vacation = ResponseVacation.from_json(d["vacation"]) + return result + + + +## The dates when the broadcaster is on vacation and not streaming. Is set to **null** if vacation mode is not enabled. +## #/components/schemas/UpdateChannelStreamScheduleSegmentResponse/Data/Vacation +class ResponseVacation extends TwitchData: + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation starts. + @export var start_time: String: + set(val): + start_time = val + track_data(&"start_time", val) + + ## The UTC date and time (in RFC3339 format) of when the broadcaster’s vacation ends. + @export var end_time: String: + set(val): + end_time = val + track_data(&"end_time", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_start_time: String, _end_time: String) -> ResponseVacation: + var response_vacation: ResponseVacation = ResponseVacation.new() + response_vacation.start_time = _start_time + response_vacation.end_time = _end_time + return response_vacation + + + static func from_json(d: Dictionary) -> ResponseVacation: + var result: ResponseVacation = ResponseVacation.new() + if d.get("start_time", null) != null: + result.start_time = d["start_time"] + if d.get("end_time", null) != null: + result.end_time = d["end_time"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd.uid b/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd.uid new file mode 100644 index 00000000..9293d21c --- /dev/null +++ b/addons/twitcher/generated/twitch_update_channel_stream_schedule_segment.gd.uid @@ -0,0 +1 @@ +uid://c2ner7gducdjx diff --git a/addons/twitcher/generated/twitch_update_chat_settings.gd b/addons/twitcher/generated/twitch_update_chat_settings.gd new file mode 100644 index 00000000..b23db2b8 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_chat_settings.gd @@ -0,0 +1,147 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateChatSettings + + + +## +## #/components/schemas/UpdateChatSettingsBody +class Body extends TwitchData: + + ## A Boolean value that determines whether chat messages must contain only emotes. + ## + ## Set to **true** if only emotes are allowed; otherwise, **false**. The default is **false**. + @export var emote_mode: bool: + set(val): + emote_mode = val + track_data(&"emote_mode", val) + + ## A Boolean value that determines whether the broadcaster restricts the chat room to followers only. + ## + ## Set to **true** if the broadcaster restricts the chat room to followers only; otherwise, **false**. The default is **true**. + ## + ## To specify how long users must follow the broadcaster before being able to participate in the chat room, see the `follower_mode_duration` field. + @export var follower_mode: bool: + set(val): + follower_mode = val + track_data(&"follower_mode", val) + + ## The length of time, in minutes, that users must follow the broadcaster before being able to participate in the chat room. Set only if `follower_mode` is **true**. Possible values are: 0 (no restriction) through 129600 (3 months). The default is 0. + @export var follower_mode_duration: int: + set(val): + follower_mode_duration = val + track_data(&"follower_mode_duration", val) + + ## A Boolean value that determines whether the broadcaster adds a short delay before chat messages appear in the chat room. This gives chat moderators and bots a chance to remove them before viewers can see the message. + ## + ## Set to **true** if the broadcaster applies a delay; otherwise, **false**. The default is **false**. + ## + ## To specify the length of the delay, see the `non_moderator_chat_delay_duration` field. + @export var non_moderator_chat_delay: bool: + set(val): + non_moderator_chat_delay = val + track_data(&"non_moderator_chat_delay", val) + + ## The amount of time, in seconds, that messages are delayed before appearing in chat. Set only if `non_moderator_chat_delay` is **true**. Possible values are: + ## + ## * 2 — 2 second delay (recommended) + ## * 4 — 4 second delay + ## * 6 — 6 second delay + @export var non_moderator_chat_delay_duration: int: + set(val): + non_moderator_chat_delay_duration = val + track_data(&"non_moderator_chat_delay_duration", val) + + ## A Boolean value that determines whether the broadcaster limits how often users in the chat room are allowed to send messages. Set to **true** if the broadcaster applies a wait period between messages; otherwise, **false**. The default is **false**. + ## + ## To specify the delay, see the `slow_mode_wait_time` field. + @export var slow_mode: bool: + set(val): + slow_mode = val + track_data(&"slow_mode", val) + + ## The amount of time, in seconds, that users must wait between sending messages. Set only if `slow_mode` is **true**. + ## + ## Possible values are: 3 (3 second delay) through 120 (2 minute delay). The default is 30 seconds. + @export var slow_mode_wait_time: int: + set(val): + slow_mode_wait_time = val + track_data(&"slow_mode_wait_time", val) + + ## A Boolean value that determines whether only users that subscribe to the broadcaster’s channel may talk in the chat room. + ## + ## Set to **true** if the broadcaster restricts the chat room to subscribers only; otherwise, **false**. The default is **false**. + @export var subscriber_mode: bool: + set(val): + subscriber_mode = val + track_data(&"subscriber_mode", val) + + ## A Boolean value that determines whether the broadcaster requires users to post only unique messages in the chat room. + ## + ## Set to **true** if the broadcaster allows only unique messages; otherwise, **false**. The default is **false**. + @export var unique_chat_mode: bool: + set(val): + unique_chat_mode = val + track_data(&"unique_chat_mode", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("emote_mode", null) != null: + result.emote_mode = d["emote_mode"] + if d.get("follower_mode", null) != null: + result.follower_mode = d["follower_mode"] + if d.get("follower_mode_duration", null) != null: + result.follower_mode_duration = d["follower_mode_duration"] + if d.get("non_moderator_chat_delay", null) != null: + result.non_moderator_chat_delay = d["non_moderator_chat_delay"] + if d.get("non_moderator_chat_delay_duration", null) != null: + result.non_moderator_chat_delay_duration = d["non_moderator_chat_delay_duration"] + if d.get("slow_mode", null) != null: + result.slow_mode = d["slow_mode"] + if d.get("slow_mode_wait_time", null) != null: + result.slow_mode_wait_time = d["slow_mode_wait_time"] + if d.get("subscriber_mode", null) != null: + result.subscriber_mode = d["subscriber_mode"] + if d.get("unique_chat_mode", null) != null: + result.unique_chat_mode = d["unique_chat_mode"] + return result + + + +## +## #/components/schemas/UpdateChatSettingsResponse +class Response extends TwitchData: + + ## The list of chat settings. The list contains a single object with all the settings. + @export var data: Array[TwitchChatSettingsUpdated]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchChatSettingsUpdated]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchChatSettingsUpdated.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_chat_settings.gd.uid b/addons/twitcher/generated/twitch_update_chat_settings.gd.uid new file mode 100644 index 00000000..94f595c6 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_chat_settings.gd.uid @@ -0,0 +1 @@ +uid://tpyjcv8fnl1q diff --git a/addons/twitcher/generated/twitch_update_conduit_shards.gd b/addons/twitcher/generated/twitch_update_conduit_shards.gd new file mode 100644 index 00000000..6fbc40ce --- /dev/null +++ b/addons/twitcher/generated/twitch_update_conduit_shards.gd @@ -0,0 +1,343 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateConduitShards + + + +## +## #/components/schemas/UpdateConduitShardsBody +class Body extends TwitchData: + + ## Conduit ID. + @export var conduit_id: String: + set(val): + conduit_id = val + track_data(&"conduit_id", val) + + ## List of shards to update. + @export var shards: Array[BodyShards]: + set(val): + shards = val + track_data(&"shards", val) + + + + ## Constructor with all required fields. + static func create(_conduit_id: String, _shards: Array[BodyShards]) -> Body: + var body: Body = Body.new() + body.conduit_id = _conduit_id + body.shards = _shards + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("conduit_id", null) != null: + result.conduit_id = d["conduit_id"] + if d.get("shards", null) != null: + for value in d["shards"]: + result.shards.append(BodyShards.from_json(value)) + return result + + + +## List of shards to update. +## #/components/schemas/UpdateConduitShardsBody/Shards +class BodyShards extends TwitchData: + + ## Shard ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The transport details that you want Twitch to use when sending you notifications. + @export var transport: BodyTransport: + set(val): + transport = val + track_data(&"transport", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _transport: BodyTransport) -> BodyShards: + var body_shards: BodyShards = BodyShards.new() + body_shards.id = _id + body_shards.transport = _transport + return body_shards + + + static func from_json(d: Dictionary) -> BodyShards: + var result: BodyShards = BodyShards.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("transport", null) != null: + result.transport = BodyTransport.from_json(d["transport"]) + return result + + + +## The transport details that you want Twitch to use when sending you notifications. +## #/components/schemas/UpdateConduitShardsBody/Shards/Transport +class BodyTransport extends TwitchData: + + ## The transport method. Possible values are: + ## + ## * webhook + ## * websocket + @export var method: String: + set(val): + method = val + track_data(&"method", val) + + ## The callback URL where the notifications are sent. The URL must use the HTTPS protocol and port 443\. See Processing an event.Specify this field only if method is set to webhook.NOTE: Redirects are not followed. + @export var callback: String: + set(val): + callback = val + track_data(&"callback", val) + + ## The secret used to verify the signature. The secret must be an ASCII string that’s a minimum of 10 characters long and a maximum of 100 characters long. For information about how the secret is used, see Verifying the event message.Specify this field only if method is set to webhook. + @export var secret: String: + set(val): + secret = val + track_data(&"secret", val) + + ## An ID that identifies the WebSocket to send notifications to. When you connect to EventSub using WebSockets, the server returns the ID in the Welcome message.Specify this field only if method is set to websocket. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + + + ## Constructor with all required fields. + static func create() -> BodyTransport: + var body_transport: BodyTransport = BodyTransport.new() + return body_transport + + + static func from_json(d: Dictionary) -> BodyTransport: + var result: BodyTransport = BodyTransport.new() + if d.get("method", null) != null: + result.method = d["method"] + if d.get("callback", null) != null: + result.callback = d["callback"] + if d.get("secret", null) != null: + result.secret = d["secret"] + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + return result + + + +## +## #/components/schemas/UpdateConduitShardsResponse +class Response extends TwitchData: + + ## List of successful shard updates. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + + ## List of unsuccessful updates. + @export var errors: Array[ResponseErrors]: + set(val): + errors = val + track_data(&"errors", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData], _errors: Array[ResponseErrors]) -> Response: + var response: Response = Response.new() + response.data = _data + response.errors = _errors + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + if d.get("errors", null) != null: + for value in d["errors"]: + result.errors.append(ResponseErrors.from_json(value)) + return result + + + +## List of successful shard updates. +## #/components/schemas/UpdateConduitShardsResponse/Data +class ResponseData extends TwitchData: + + ## Shard ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The shard status. The subscriber receives events only for enabled shards. Possible values are: + ## + ## * enabled — The shard is enabled. + ## * webhook\_callback\_verification\_pending — The shard is pending verification of the specified callback URL. + ## * webhook\_callback\_verification\_failed — The specified callback URL failed verification. + ## * notification\_failures\_exceeded — The notification delivery failure rate was too high. + ## * websocket\_disconnected — The client closed the connection. + ## * websocket\_failed\_ping\_pong — The client failed to respond to a ping message. + ## * websocket\_received\_inbound\_traffic — The client sent a non-pong message. Clients may only send pong messages (and only in response to a ping message). + ## * websocket\_internal\_error — The Twitch WebSocket server experienced an unexpected error. + ## * websocket\_network\_timeout — The Twitch WebSocket server timed out writing the message to the client. + ## * websocket\_network\_error — The Twitch WebSocket server experienced a network error writing the message to the client. + ## * websocket\_failed\_to\_reconnect - The client failed to reconnect to the Twitch WebSocket server within the required time after a Reconnect Message. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + ## The transport details used to send the notifications. + @export var transport: ResponseTransport: + set(val): + transport = val + track_data(&"transport", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _status: String, _transport: ResponseTransport) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.status = _status + response_data.transport = _transport + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("status", null) != null: + result.status = d["status"] + if d.get("transport", null) != null: + result.transport = ResponseTransport.from_json(d["transport"]) + return result + + + +## The transport details used to send the notifications. +## #/components/schemas/UpdateConduitShardsResponse/Data/Transport +class ResponseTransport extends TwitchData: + + ## The transport method. Possible values are: + ## + ## * webhook + ## * websocket + @export var method: String: + set(val): + method = val + track_data(&"method", val) + + ## The callback URL where the notifications are sent. Included only if method is set to webhook. + @export var callback: String: + set(val): + callback = val + track_data(&"callback", val) + + ## An ID that identifies the WebSocket that notifications are sent to. Included only if method is set to websocket. + @export var session_id: String: + set(val): + session_id = val + track_data(&"session_id", val) + + ## The UTC date and time that the WebSocket connection was established. Included only if method is set to websocket. + @export var connected_at: String: + set(val): + connected_at = val + track_data(&"connected_at", val) + + ## The UTC date and time that the WebSocket connection was lost. Included only if method is set to websocket. + @export var disconnected_at: String: + set(val): + disconnected_at = val + track_data(&"disconnected_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_method: String) -> ResponseTransport: + var response_transport: ResponseTransport = ResponseTransport.new() + response_transport.method = _method + return response_transport + + + static func from_json(d: Dictionary) -> ResponseTransport: + var result: ResponseTransport = ResponseTransport.new() + if d.get("method", null) != null: + result.method = d["method"] + if d.get("callback", null) != null: + result.callback = d["callback"] + if d.get("session_id", null) != null: + result.session_id = d["session_id"] + if d.get("connected_at", null) != null: + result.connected_at = d["connected_at"] + if d.get("disconnected_at", null) != null: + result.disconnected_at = d["disconnected_at"] + return result + + + +## List of unsuccessful updates. +## #/components/schemas/UpdateConduitShardsResponse/Errors +class ResponseErrors extends TwitchData: + + ## Shard ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The error that occurred while updating the shard. Possible errors: + ## + ## * The length of the string in the secret field is not valid. + ## * The URL in the transport's callback field is not valid. The URL must use the HTTPS protocol and the 443 port number. + ## * The value specified in the method field is not valid. + ## * The callback field is required if you specify the webhook transport method. + ## * The session\_id field is required if you specify the WebSocket transport method. + ## * The websocket session is not connected. + ## * The shard id is outside of the conduit’s range. + @export var message: String: + set(val): + message = val + track_data(&"message", val) + + ## Error codes used to represent a specific error condition while attempting to update shards. + @export var code: String: + set(val): + code = val + track_data(&"code", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _message: String, _code: String) -> ResponseErrors: + var response_errors: ResponseErrors = ResponseErrors.new() + response_errors.id = _id + response_errors.message = _message + response_errors.code = _code + return response_errors + + + static func from_json(d: Dictionary) -> ResponseErrors: + var result: ResponseErrors = ResponseErrors.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("message", null) != null: + result.message = d["message"] + if d.get("code", null) != null: + result.code = d["code"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_conduit_shards.gd.uid b/addons/twitcher/generated/twitch_update_conduit_shards.gd.uid new file mode 100644 index 00000000..d7740eb9 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_conduit_shards.gd.uid @@ -0,0 +1 @@ +uid://dacxpre5dg6l3 diff --git a/addons/twitcher/generated/twitch_update_conduits.gd b/addons/twitcher/generated/twitch_update_conduits.gd new file mode 100644 index 00000000..364e9d28 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_conduits.gd @@ -0,0 +1,107 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateConduits + + + +## +## #/components/schemas/UpdateConduitsBody +class Body extends TwitchData: + + ## Conduit ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## The new number of shards for this conduit. + @export var shard_count: int: + set(val): + shard_count = val + track_data(&"shard_count", val) + + + + ## Constructor with all required fields. + static func create(_id: String, _shard_count: int) -> Body: + var body: Body = Body.new() + body.id = _id + body.shard_count = _shard_count + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("shard_count", null) != null: + result.shard_count = d["shard_count"] + return result + + + +## +## #/components/schemas/UpdateConduitsResponse +class Response extends TwitchData: + + ## List of information about the client’s conduits. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## List of information about the client’s conduits. +## #/components/schemas/UpdateConduitsResponse/Data +class ResponseData extends TwitchData: + + ## Conduit ID. + @export var id: String: + set(val): + id = val + track_data(&"id", val) + + ## Number of shards associated with this conduit after the update. + @export var shard_count: int: + set(val): + shard_count = val + track_data(&"shard_count", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_id: String, _shard_count: int) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.id = _id + response_data.shard_count = _shard_count + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("shard_count", null) != null: + result.shard_count = d["shard_count"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_conduits.gd.uid b/addons/twitcher/generated/twitch_update_conduits.gd.uid new file mode 100644 index 00000000..2c97ed38 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_conduits.gd.uid @@ -0,0 +1 @@ +uid://d1vib5ftfiafj diff --git a/addons/twitcher/generated/twitch_update_custom_reward.gd b/addons/twitcher/generated/twitch_update_custom_reward.gd new file mode 100644 index 00000000..d09a3147 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_custom_reward.gd @@ -0,0 +1,165 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateCustomReward + + + +## +## #/components/schemas/UpdateCustomRewardBody +class Body extends TwitchData: + + ## The reward’s title. The title may contain a maximum of 45 characters and it must be unique amongst all of the broadcaster’s custom rewards. + @export var title: String: + set(val): + title = val + track_data(&"title", val) + + ## The prompt shown to the viewer when they redeem the reward. Specify a prompt if `is_user_input_required` is **true**. The prompt is limited to a maximum of 200 characters. + @export var prompt: String: + set(val): + prompt = val + track_data(&"prompt", val) + + ## The cost of the reward, in channel points. The minimum is 1 point. + @export var cost: int: + set(val): + cost = val + track_data(&"cost", val) + + ## The background color to use for the reward. Specify the color using Hex format (for example, \\#00E5CB). + @export var background_color: String: + set(val): + background_color = val + track_data(&"background_color", val) + + ## A Boolean value that indicates whether the reward is enabled. Set to **true** to enable the reward. Viewers see only enabled rewards. + @export var is_enabled: bool: + set(val): + is_enabled = val + track_data(&"is_enabled", val) + + ## A Boolean value that determines whether users must enter information to redeem the reward. Set to **true** if user input is required. See the `prompt` field. + @export var is_user_input_required: bool: + set(val): + is_user_input_required = val + track_data(&"is_user_input_required", val) + + ## A Boolean value that determines whether to limit the maximum number of redemptions allowed per live stream (see the `max_per_stream` field). Set to **true** to limit redemptions. + @export var is_max_per_stream_enabled: bool: + set(val): + is_max_per_stream_enabled = val + track_data(&"is_max_per_stream_enabled", val) + + ## The maximum number of redemptions allowed per live stream. Applied only if `is_max_per_stream_enabled` is **true**. The minimum value is 1. + @export var max_per_stream: int: + set(val): + max_per_stream = val + track_data(&"max_per_stream", val) + + ## A Boolean value that determines whether to limit the maximum number of redemptions allowed per user per stream (see `max_per_user_per_stream`). The minimum value is 1\. Set to **true** to limit redemptions. + @export var is_max_per_user_per_stream_enabled: bool: + set(val): + is_max_per_user_per_stream_enabled = val + track_data(&"is_max_per_user_per_stream_enabled", val) + + ## The maximum number of redemptions allowed per user per stream. Applied only if `is_max_per_user_per_stream_enabled` is **true**. + @export var max_per_user_per_stream: int: + set(val): + max_per_user_per_stream = val + track_data(&"max_per_user_per_stream", val) + + ## A Boolean value that determines whether to apply a cooldown period between redemptions. Set to **true** to apply a cooldown period. For the duration of the cooldown period, see `global_cooldown_seconds`. + @export var is_global_cooldown_enabled: bool: + set(val): + is_global_cooldown_enabled = val + track_data(&"is_global_cooldown_enabled", val) + + ## The cooldown period, in seconds. Applied only if `is_global_cooldown_enabled` is **true**. The minimum value is 1; however, for it to be shown in the Twitch UX, the minimum value is 60. + @export var global_cooldown_seconds: int: + set(val): + global_cooldown_seconds = val + track_data(&"global_cooldown_seconds", val) + + ## A Boolean value that determines whether to pause the reward. Set to **true** to pause the reward. Viewers can’t redeem paused rewards.. + @export var is_paused: bool: + set(val): + is_paused = val + track_data(&"is_paused", val) + + ## A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed. If **false**, status is set to UNFULFILLED and follows the normal request queue process. + @export var should_redemptions_skip_request_queue: bool: + set(val): + should_redemptions_skip_request_queue = val + track_data(&"should_redemptions_skip_request_queue", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("title", null) != null: + result.title = d["title"] + if d.get("prompt", null) != null: + result.prompt = d["prompt"] + if d.get("cost", null) != null: + result.cost = d["cost"] + if d.get("background_color", null) != null: + result.background_color = d["background_color"] + if d.get("is_enabled", null) != null: + result.is_enabled = d["is_enabled"] + if d.get("is_user_input_required", null) != null: + result.is_user_input_required = d["is_user_input_required"] + if d.get("is_max_per_stream_enabled", null) != null: + result.is_max_per_stream_enabled = d["is_max_per_stream_enabled"] + if d.get("max_per_stream", null) != null: + result.max_per_stream = d["max_per_stream"] + if d.get("is_max_per_user_per_stream_enabled", null) != null: + result.is_max_per_user_per_stream_enabled = d["is_max_per_user_per_stream_enabled"] + if d.get("max_per_user_per_stream", null) != null: + result.max_per_user_per_stream = d["max_per_user_per_stream"] + if d.get("is_global_cooldown_enabled", null) != null: + result.is_global_cooldown_enabled = d["is_global_cooldown_enabled"] + if d.get("global_cooldown_seconds", null) != null: + result.global_cooldown_seconds = d["global_cooldown_seconds"] + if d.get("is_paused", null) != null: + result.is_paused = d["is_paused"] + if d.get("should_redemptions_skip_request_queue", null) != null: + result.should_redemptions_skip_request_queue = d["should_redemptions_skip_request_queue"] + return result + + + +## +## #/components/schemas/UpdateCustomRewardResponse +class Response extends TwitchData: + + ## The list contains the single reward that you updated. + @export var data: Array[TwitchCustomReward]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCustomReward]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCustomReward.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_custom_reward.gd.uid b/addons/twitcher/generated/twitch_update_custom_reward.gd.uid new file mode 100644 index 00000000..684b8e70 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_custom_reward.gd.uid @@ -0,0 +1 @@ +uid://che5jvmhkt8nn diff --git a/addons/twitcher/generated/twitch_update_drops_entitlements.gd b/addons/twitcher/generated/twitch_update_drops_entitlements.gd new file mode 100644 index 00000000..50d813d2 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_drops_entitlements.gd @@ -0,0 +1,73 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateDropsEntitlements + + + +## +## #/components/schemas/UpdateDropsEntitlementsBody +class Body extends TwitchData: + + ## A list of IDs that identify the entitlements to update. You may specify a maximum of 100 IDs. + @export var entitlement_ids: Array[String]: + set(val): + entitlement_ids = val + track_data(&"entitlement_ids", val) + + ## The fulfillment status to set the entitlements to. Possible values are: + ## + ## * CLAIMED — The user claimed the benefit. + ## * FULFILLED — The developer granted the benefit that the user claimed. + @export var fulfillment_status: String: + set(val): + fulfillment_status = val + track_data(&"fulfillment_status", val) + + + + ## Constructor with all required fields. + static func create() -> Body: + var body: Body = Body.new() + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("entitlement_ids", null) != null: + for value in d["entitlement_ids"]: + result.entitlement_ids.append(value) + if d.get("fulfillment_status", null) != null: + result.fulfillment_status = d["fulfillment_status"] + return result + + + +## +## #/components/schemas/UpdateDropsEntitlementsResponse +class Response extends TwitchData: + + ## A list that indicates which entitlements were successfully updated and those that weren’t. + @export var data: Array[TwitchDropsEntitlementUpdated]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchDropsEntitlementUpdated]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchDropsEntitlementUpdated.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_drops_entitlements.gd.uid b/addons/twitcher/generated/twitch_update_drops_entitlements.gd.uid new file mode 100644 index 00000000..15bc71aa --- /dev/null +++ b/addons/twitcher/generated/twitch_update_drops_entitlements.gd.uid @@ -0,0 +1 @@ +uid://rwtv6ypknpaf diff --git a/addons/twitcher/generated/twitch_update_extension_bits_product.gd b/addons/twitcher/generated/twitch_update_extension_bits_product.gd new file mode 100644 index 00000000..7ab5b30a --- /dev/null +++ b/addons/twitcher/generated/twitch_update_extension_bits_product.gd @@ -0,0 +1,142 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateExtensionBitsProduct + + + +## +## #/components/schemas/UpdateExtensionBitsProductBody +class Body extends TwitchData: + + ## The product's SKU. The SKU must be unique within an extension. The product's SKU cannot be changed. The SKU may contain only alphanumeric characters, dashes (-), underscores (\_), and periods (.) and is limited to a maximum of 255 characters. No spaces. + @export var sku: String: + set(val): + sku = val + track_data(&"sku", val) + + ## An object that contains the product's cost information. + @export var cost: BodyCost: + set(val): + cost = val + track_data(&"cost", val) + + ## The product's name as displayed in the extension. The maximum length is 255 characters. + @export var display_name: String: + set(val): + display_name = val + track_data(&"display_name", val) + + ## A Boolean value that indicates whether the product is in development. Set to **true** if the product is in development and not available for public use. The default is **false**. + @export var in_development: bool: + set(val): + in_development = val + track_data(&"in_development", val) + + ## The date and time, in RFC3339 format, when the product expires. If not set, the product does not expire. To disable the product, set the expiration date to a date in the past. + @export var expiration: String: + set(val): + expiration = val + track_data(&"expiration", val) + + ## A Boolean value that determines whether Bits product purchase events are broadcast to all instances of the extension on a channel. The events are broadcast via the `onTransactionComplete` helper callback. The default is **false**. + @export var is_broadcast: bool: + set(val): + is_broadcast = val + track_data(&"is_broadcast", val) + + + + ## Constructor with all required fields. + static func create(_sku: String, _cost: BodyCost, _display_name: String) -> Body: + var body: Body = Body.new() + body.sku = _sku + body.cost = _cost + body.display_name = _display_name + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("sku", null) != null: + result.sku = d["sku"] + if d.get("cost", null) != null: + result.cost = BodyCost.from_json(d["cost"]) + if d.get("display_name", null) != null: + result.display_name = d["display_name"] + if d.get("in_development", null) != null: + result.in_development = d["in_development"] + if d.get("expiration", null) != null: + result.expiration = d["expiration"] + if d.get("is_broadcast", null) != null: + result.is_broadcast = d["is_broadcast"] + return result + + + +## An object that contains the product's cost information. +## #/components/schemas/UpdateExtensionBitsProductBody/Cost +class BodyCost extends TwitchData: + + ## The product's price. + @export var amount: int: + set(val): + amount = val + track_data(&"amount", val) + + ## The type of currency. Possible values are: + ## + ## * bits — The minimum price is 1 and the maximum is 10000. + @export var type: String: + set(val): + type = val + track_data(&"type", val) + + + + ## Constructor with all required fields. + static func create(_amount: int, _type: String) -> BodyCost: + var body_cost: BodyCost = BodyCost.new() + body_cost.amount = _amount + body_cost.type = _type + return body_cost + + + static func from_json(d: Dictionary) -> BodyCost: + var result: BodyCost = BodyCost.new() + if d.get("amount", null) != null: + result.amount = d["amount"] + if d.get("type", null) != null: + result.type = d["type"] + return result + + + +## +## #/components/schemas/UpdateExtensionBitsProductResponse +class Response extends TwitchData: + + ## A list of Bits products that the extension created. The list is in ascending SKU order. The list is empty if the extension hasn't created any products or they're all expired or disabled. + @export var data: Array[TwitchExtensionBitsProduct]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchExtensionBitsProduct]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchExtensionBitsProduct.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_extension_bits_product.gd.uid b/addons/twitcher/generated/twitch_update_extension_bits_product.gd.uid new file mode 100644 index 00000000..6be04760 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_extension_bits_product.gd.uid @@ -0,0 +1 @@ +uid://c1fry1o2hvild diff --git a/addons/twitcher/generated/twitch_update_guest_star_slot.gd b/addons/twitcher/generated/twitch_update_guest_star_slot.gd new file mode 100644 index 00000000..b50e6390 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_guest_star_slot.gd @@ -0,0 +1,33 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateGuestStarSlot + + + +## All optional parameters for TwitchAPI.update_guest_star_slot +## #/components/schemas/UpdateGuestStarSlotOpt +class Opt extends TwitchData: + + ## The slot to move this user assignment to. If the destination slot is occupied, the user assigned will be swapped into `source_slot_id`. + @export var destination_slot_id: String: + set(val): + destination_slot_id = val + track_data(&"destination_slot_id", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("destination_slot_id", null) != null: + result.destination_slot_id = d["destination_slot_id"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_guest_star_slot.gd.uid b/addons/twitcher/generated/twitch_update_guest_star_slot.gd.uid new file mode 100644 index 00000000..3c5b2120 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_guest_star_slot.gd.uid @@ -0,0 +1 @@ +uid://chj72jmp18ygf diff --git a/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd b/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd new file mode 100644 index 00000000..3dd139ea --- /dev/null +++ b/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd @@ -0,0 +1,57 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateGuestStarSlotSettings + + + +## All optional parameters for TwitchAPI.update_guest_star_slot_settings +## #/components/schemas/UpdateGuestStarSlotSettingsOpt +class Opt extends TwitchData: + + ## Flag indicating whether the slot is allowed to share their audio with the rest of the session. If false, the slot will be muted in any views containing the slot. + @export var is_audio_enabled: bool: + set(val): + is_audio_enabled = val + track_data(&"is_audio_enabled", val) + + ## Flag indicating whether the slot is allowed to share their video with the rest of the session. If false, the slot will have no video shared in any views containing the slot. + @export var is_video_enabled: bool: + set(val): + is_video_enabled = val + track_data(&"is_video_enabled", val) + + ## Flag indicating whether the user assigned to this slot is visible/can be heard from any public subscriptions. Generally, this determines whether or not the slot is enabled in any broadcasting software integrations. + @export var is_live: bool: + set(val): + is_live = val + track_data(&"is_live", val) + + ## Value from 0-100 that controls the audio volume for shared views containing the slot. + @export var volume: int: + set(val): + volume = val + track_data(&"volume", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("is_audio_enabled", null) != null: + result.is_audio_enabled = d["is_audio_enabled"] + if d.get("is_video_enabled", null) != null: + result.is_video_enabled = d["is_video_enabled"] + if d.get("is_live", null) != null: + result.is_live = d["is_live"] + if d.get("volume", null) != null: + result.volume = d["volume"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd.uid b/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd.uid new file mode 100644 index 00000000..9f9c9cf0 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_guest_star_slot_settings.gd.uid @@ -0,0 +1 @@ +uid://cvgocdv48imwq diff --git a/addons/twitcher/generated/twitch_update_redemption_status.gd b/addons/twitcher/generated/twitch_update_redemption_status.gd new file mode 100644 index 00000000..3595688e --- /dev/null +++ b/addons/twitcher/generated/twitch_update_redemption_status.gd @@ -0,0 +1,67 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateRedemptionStatus + + + +## +## #/components/schemas/UpdateRedemptionStatusBody +class Body extends TwitchData: + + ## The status to set the redemption to. Possible values are: + ## + ## * CANCELED + ## * FULFILLED + ## + ## Setting the status to CANCELED refunds the user’s channel points. + @export var status: String: + set(val): + status = val + track_data(&"status", val) + + + + ## Constructor with all required fields. + static func create(_status: String) -> Body: + var body: Body = Body.new() + body.status = _status + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("status", null) != null: + result.status = d["status"] + return result + + + +## +## #/components/schemas/UpdateRedemptionStatusResponse +class Response extends TwitchData: + + ## The list contains the single redemption that you updated. + @export var data: Array[TwitchCustomRewardRedemption]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchCustomRewardRedemption]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchCustomRewardRedemption.from_json(value)) + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_redemption_status.gd.uid b/addons/twitcher/generated/twitch_update_redemption_status.gd.uid new file mode 100644 index 00000000..fe9b1573 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_redemption_status.gd.uid @@ -0,0 +1 @@ +uid://x8dfpnom73ce diff --git a/addons/twitcher/generated/twitch_update_shield_mode_status.gd b/addons/twitcher/generated/twitch_update_shield_mode_status.gd new file mode 100644 index 00000000..07647bbe --- /dev/null +++ b/addons/twitcher/generated/twitch_update_shield_mode_status.gd @@ -0,0 +1,125 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateShieldModeStatus + + + +## +## #/components/schemas/UpdateShieldModeStatusBody +class Body extends TwitchData: + + ## A Boolean value that determines whether to activate Shield Mode. Set to **true** to activate Shield Mode; otherwise, **false** to deactivate Shield Mode. + @export var is_active: bool: + set(val): + is_active = val + track_data(&"is_active", val) + + + + ## Constructor with all required fields. + static func create(_is_active: bool) -> Body: + var body: Body = Body.new() + body.is_active = _is_active + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("is_active", null) != null: + result.is_active = d["is_active"] + return result + + + +## +## #/components/schemas/UpdateShieldModeStatusResponse +class Response extends TwitchData: + + ## A list that contains a single object with the broadcaster’s updated Shield Mode status. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains a single object with the broadcaster’s updated Shield Mode status. +## #/components/schemas/UpdateShieldModeStatusResponse/Data +class ResponseData extends TwitchData: + + ## A Boolean value that determines whether Shield Mode is active. Is **true** if Shield Mode is active; otherwise, **false**. + @export var is_active: bool: + set(val): + is_active = val + track_data(&"is_active", val) + + ## An ID that identifies the moderator that last activated Shield Mode. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The moderator’s login name. + @export var moderator_login: String: + set(val): + moderator_login = val + track_data(&"moderator_login", val) + + ## The moderator’s display name. + @export var moderator_name: String: + set(val): + moderator_name = val + track_data(&"moderator_name", val) + + ## The UTC timestamp (in RFC3339 format) of when Shield Mode was last activated. + @export var last_activated_at: String: + set(val): + last_activated_at = val + track_data(&"last_activated_at", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_is_active: bool, _moderator_id: String, _moderator_login: String, _moderator_name: String, _last_activated_at: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.is_active = _is_active + response_data.moderator_id = _moderator_id + response_data.moderator_login = _moderator_login + response_data.moderator_name = _moderator_name + response_data.last_activated_at = _last_activated_at + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("is_active", null) != null: + result.is_active = d["is_active"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("moderator_login", null) != null: + result.moderator_login = d["moderator_login"] + if d.get("moderator_name", null) != null: + result.moderator_name = d["moderator_name"] + if d.get("last_activated_at", null) != null: + result.last_activated_at = d["last_activated_at"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_shield_mode_status.gd.uid b/addons/twitcher/generated/twitch_update_shield_mode_status.gd.uid new file mode 100644 index 00000000..7804ee46 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_shield_mode_status.gd.uid @@ -0,0 +1 @@ +uid://b77rn5bibl3pg diff --git a/addons/twitcher/generated/twitch_update_user.gd b/addons/twitcher/generated/twitch_update_user.gd new file mode 100644 index 00000000..42137f94 --- /dev/null +++ b/addons/twitcher/generated/twitch_update_user.gd @@ -0,0 +1,63 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateUser + + + +## +## #/components/schemas/UpdateUserResponse +class Response extends TwitchData: + + ## A list contains the single user that you updated. + @export var data: Array[TwitchUser]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[TwitchUser]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(TwitchUser.from_json(value)) + return result + + + +## All optional parameters for TwitchAPI.update_user +## #/components/schemas/UpdateUserOpt +class Opt extends TwitchData: + + ## The string to update the channel’s description to. The description is limited to a maximum of 300 characters. + ## + ## To remove the description, specify this parameter but don’t set it’s value (for example, `?description=`). + @export var description: String: + set(val): + description = val + track_data(&"description", val) + + + + ## Constructor with all required fields. + static func create() -> Opt: + var opt: Opt = Opt.new() + return opt + + + static func from_json(d: Dictionary) -> Opt: + var result: Opt = Opt.new() + if d.get("description", null) != null: + result.description = d["description"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_user.gd.uid b/addons/twitcher/generated/twitch_update_user.gd.uid new file mode 100644 index 00000000..d4868f4c --- /dev/null +++ b/addons/twitcher/generated/twitch_update_user.gd.uid @@ -0,0 +1 @@ +uid://d2rgoccbwdvs2 diff --git a/addons/twitcher/generated/twitch_update_user_extensions.gd b/addons/twitcher/generated/twitch_update_user_extensions.gd new file mode 100644 index 00000000..5d02762b --- /dev/null +++ b/addons/twitcher/generated/twitch_update_user_extensions.gd @@ -0,0 +1,156 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchUpdateUserExtensions + + + +## +## #/components/schemas/UpdateUserExtensionsBody +class Body extends TwitchData: + + ## The extensions to update. The `data` field is a dictionary of extension types. The dictionary’s possible keys are: panel, overlay, or component. The key’s value is a dictionary of extensions. + ## + ## For the extension’s dictionary, the key is a sequential number beginning with 1\. For panel and overlay extensions, the key’s value is an object that contains the following fields: `active` (true/false), `id` (the extension’s ID), and `version` (the extension’s version). + ## + ## For component extensions, the key’s value includes the above fields plus the `x` and `y` fields, which identify the coordinate where the extension is placed. + @export var data: BodyData: + set(val): + data = val + track_data(&"data", val) + + + + ## Constructor with all required fields. + static func create(_data: BodyData) -> Body: + var body: Body = Body.new() + body.data = _data + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("data", null) != null: + result.data = BodyData.from_json(d["data"]) + return result + + + +## The extensions to update. The `data` field is a dictionary of extension types. The dictionary’s possible keys are: panel, overlay, or component. The key’s value is a dictionary of extensions. +## +## For the extension’s dictionary, the key is a sequential number beginning with 1\. For panel and overlay extensions, the key’s value is an object that contains the following fields: `active` (true/false), `id` (the extension’s ID), and `version` (the extension’s version). +## +## For component extensions, the key’s value includes the above fields plus the `x` and `y` fields, which identify the coordinate where the extension is placed. +## #/components/schemas/UpdateUserExtensionsBody/Data +class BodyData extends TwitchData: + + ## + @export var panel: Dictionary: + set(val): + panel = val + track_data(&"panel", val) + + ## + @export var overlay: Dictionary: + set(val): + overlay = val + track_data(&"overlay", val) + + ## + @export var component: Dictionary: + set(val): + component = val + track_data(&"component", val) + + + + ## Constructor with all required fields. + static func create() -> BodyData: + var body_data: BodyData = BodyData.new() + return body_data + + + static func from_json(d: Dictionary) -> BodyData: + var result: BodyData = BodyData.new() + if d.get("panel", null) != null: + result.panel = d["panel"] + if d.get("overlay", null) != null: + result.overlay = d["overlay"] + if d.get("component", null) != null: + result.component = d["component"] + return result + + + +## +## #/components/schemas/UpdateUserExtensionsResponse +class Response extends TwitchData: + + ## The extensions that the broadcaster updated. + @export var data: ResponseData: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: ResponseData) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + result.data = ResponseData.from_json(d["data"]) + return result + + + +## The extensions that the broadcaster updated. +## #/components/schemas/UpdateUserExtensionsResponse/Data +class ResponseData extends TwitchData: + + ## A dictionary that contains the data for a panel extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the panel’s data for each key. + @export var panel: Dictionary: + set(val): + panel = val + track_data(&"panel", val) + + ## A dictionary that contains the data for a video-overlay extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the overlay’s data for each key. + @export var overlay: Dictionary: + set(val): + overlay = val + track_data(&"overlay", val) + + ## A dictionary that contains the data for a video-component extension. The dictionary’s key is a sequential number beginning with 1\. The following fields contain the component’s data for each key. + @export var component: Dictionary: + set(val): + component = val + track_data(&"component", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_panel: Dictionary, _overlay: Dictionary, _component: Dictionary) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.panel = _panel + response_data.overlay = _overlay + response_data.component = _component + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("panel", null) != null: + result.panel = d["panel"] + if d.get("overlay", null) != null: + result.overlay = d["overlay"] + if d.get("component", null) != null: + result.component = d["component"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_update_user_extensions.gd.uid b/addons/twitcher/generated/twitch_update_user_extensions.gd.uid new file mode 100644 index 00000000..3c6258da --- /dev/null +++ b/addons/twitcher/generated/twitch_update_user_extensions.gd.uid @@ -0,0 +1 @@ +uid://bgfvvk40bo5yt diff --git a/addons/twitcher/generated/twitch_user.gd b/addons/twitcher/generated/twitch_user.gd new file mode 100644 index 00000000..518ae3f4 --- /dev/null +++ b/addons/twitcher/generated/twitch_user.gd @@ -0,0 +1,131 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/User +class_name TwitchUser + +## An ID that identifies the user. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The user's login name. +@export var login: String: + set(val): + login = val + track_data(&"login", val) + +## The user's display name. +@export var display_name: String: + set(val): + display_name = val + track_data(&"display_name", val) + +## The type of user. Possible values are: +## +## * admin — Twitch administrator +## * global\_mod +## * staff — Twitch staff +## * "" — Normal user +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The type of broadcaster. Possible values are: +## +## * affiliate — An [affiliate broadcaster](https://help.twitch.tv/s/article/joining-the-affiliate-program) +## * partner — A [partner broadcaster](https://help.twitch.tv/s/article/partner-program-overview) +## * "" — A normal broadcaster +@export var broadcaster_type: String: + set(val): + broadcaster_type = val + track_data(&"broadcaster_type", val) + +## The user's description of their channel. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + +## A URL to the user's profile image. +@export var profile_image_url: String: + set(val): + profile_image_url = val + track_data(&"profile_image_url", val) + +## A URL to the user's offline image. +@export var offline_image_url: String: + set(val): + offline_image_url = val + track_data(&"offline_image_url", val) + +## The number of times the user's channel has been viewed. +## +## **NOTE**: This field has been deprecated (see [Get Users API endpoint – "view\_count" deprecation](https://discuss.dev.twitch.tv/t/get-users-api-endpoint-view-count-deprecation/37777)). Any data in this field is not valid and should not be used. +@export var view_count: int: + set(val): + view_count = val + track_data(&"view_count", val) + +## The user's verified email address. The object includes this field only if the user access token includes the **user:read:email** scope. +## +## If the request contains more than one user, only the user associated with the access token that provided consent will include an email address — the email address for all other users will be empty. +@export var email: String: + set(val): + email = val + track_data(&"email", val) + +## The UTC date and time that the user's account was created. The timestamp is in RFC3339 format. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + + + +## Constructor with all required fields. +static func create(_id: String, _login: String, _display_name: String, _type: String, _broadcaster_type: String, _description: String, _profile_image_url: String, _offline_image_url: String, _view_count: int, _created_at: String) -> TwitchUser: + var twitch_user: TwitchUser = TwitchUser.new() + twitch_user.id = _id + twitch_user.login = _login + twitch_user.display_name = _display_name + twitch_user.type = _type + twitch_user.broadcaster_type = _broadcaster_type + twitch_user.description = _description + twitch_user.profile_image_url = _profile_image_url + twitch_user.offline_image_url = _offline_image_url + twitch_user.view_count = _view_count + twitch_user.created_at = _created_at + return twitch_user + + +static func from_json(d: Dictionary) -> TwitchUser: + var result: TwitchUser = TwitchUser.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("login", null) != null: + result.login = d["login"] + if d.get("display_name", null) != null: + result.display_name = d["display_name"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("broadcaster_type", null) != null: + result.broadcaster_type = d["broadcaster_type"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("profile_image_url", null) != null: + result.profile_image_url = d["profile_image_url"] + if d.get("offline_image_url", null) != null: + result.offline_image_url = d["offline_image_url"] + if d.get("view_count", null) != null: + result.view_count = d["view_count"] + if d.get("email", null) != null: + result.email = d["email"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + return result diff --git a/addons/twitcher/generated/twitch_user.gd.uid b/addons/twitcher/generated/twitch_user.gd.uid new file mode 100644 index 00000000..3791ea2d --- /dev/null +++ b/addons/twitcher/generated/twitch_user.gd.uid @@ -0,0 +1 @@ +uid://cnmohrdxqihr0 diff --git a/addons/twitcher/generated/twitch_user_block_list.gd b/addons/twitcher/generated/twitch_user_block_list.gd new file mode 100644 index 00000000..cc295ab5 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_block_list.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserBlockList +class_name TwitchUserBlockList + +## An ID that identifies the blocked user. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The blocked user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The blocked user’s display name. +@export var display_name: String: + set(val): + display_name = val + track_data(&"display_name", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _display_name: String) -> TwitchUserBlockList: + var twitch_user_block_list: TwitchUserBlockList = TwitchUserBlockList.new() + twitch_user_block_list.user_id = _user_id + twitch_user_block_list.user_login = _user_login + twitch_user_block_list.display_name = _display_name + return twitch_user_block_list + + +static func from_json(d: Dictionary) -> TwitchUserBlockList: + var result: TwitchUserBlockList = TwitchUserBlockList.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("display_name", null) != null: + result.display_name = d["display_name"] + return result diff --git a/addons/twitcher/generated/twitch_user_block_list.gd.uid b/addons/twitcher/generated/twitch_user_block_list.gd.uid new file mode 100644 index 00000000..1d8db9e3 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_block_list.gd.uid @@ -0,0 +1 @@ +uid://clxcu2w6xi4uo diff --git a/addons/twitcher/generated/twitch_user_chat_color.gd b/addons/twitcher/generated/twitch_user_chat_color.gd new file mode 100644 index 00000000..451707a6 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_chat_color.gd @@ -0,0 +1,56 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserChatColor +class_name TwitchUserChatColor + +## An ID that uniquely identifies the user. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The Hex color code that the user uses in chat for their name. If the user hasn’t specified a color in their settings, the string is empty. +@export var color: String: + set(val): + color = val + track_data(&"color", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _user_name: String, _color: String) -> TwitchUserChatColor: + var twitch_user_chat_color: TwitchUserChatColor = TwitchUserChatColor.new() + twitch_user_chat_color.user_id = _user_id + twitch_user_chat_color.user_login = _user_login + twitch_user_chat_color.user_name = _user_name + twitch_user_chat_color.color = _color + return twitch_user_chat_color + + +static func from_json(d: Dictionary) -> TwitchUserChatColor: + var result: TwitchUserChatColor = TwitchUserChatColor.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("color", null) != null: + result.color = d["color"] + return result diff --git a/addons/twitcher/generated/twitch_user_chat_color.gd.uid b/addons/twitcher/generated/twitch_user_chat_color.gd.uid new file mode 100644 index 00000000..8834628b --- /dev/null +++ b/addons/twitcher/generated/twitch_user_chat_color.gd.uid @@ -0,0 +1 @@ +uid://ct4qim6dtv5d0 diff --git a/addons/twitcher/generated/twitch_user_extension.gd b/addons/twitcher/generated/twitch_user_extension.gd new file mode 100644 index 00000000..ce64afb2 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension.gd @@ -0,0 +1,71 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtension +class_name TwitchUserExtension + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension's version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The extension's name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## A Boolean value that determines whether the extension is configured and can be activated. Is **true** if the extension is configured and can be activated. +@export var can_activate: bool: + set(val): + can_activate = val + track_data(&"can_activate", val) + +## The extension types that you can activate for this extension. Possible values are: +## +## * component +## * mobile +## * overlay +## * panel +@export var type: Array[String]: + set(val): + type = val + track_data(&"type", val) + + + +## Constructor with all required fields. +static func create(_id: String, _version: String, _name: String, _can_activate: bool, _type: Array[String]) -> TwitchUserExtension: + var twitch_user_extension: TwitchUserExtension = TwitchUserExtension.new() + twitch_user_extension.id = _id + twitch_user_extension.version = _version + twitch_user_extension.name = _name + twitch_user_extension.can_activate = _can_activate + twitch_user_extension.type = _type + return twitch_user_extension + + +static func from_json(d: Dictionary) -> TwitchUserExtension: + var result: TwitchUserExtension = TwitchUserExtension.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("can_activate", null) != null: + result.can_activate = d["can_activate"] + if d.get("type", null) != null: + for value in d["type"]: + result.type.append(value) + return result diff --git a/addons/twitcher/generated/twitch_user_extension.gd.uid b/addons/twitcher/generated/twitch_user_extension.gd.uid new file mode 100644 index 00000000..93e065fa --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension.gd.uid @@ -0,0 +1 @@ +uid://ct2bdq3yyj1lx diff --git a/addons/twitcher/generated/twitch_user_extension_component.gd b/addons/twitcher/generated/twitch_user_extension_component.gd new file mode 100644 index 00000000..990dc0da --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_component.gd @@ -0,0 +1,69 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionComponent +class_name TwitchUserExtensionComponent + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured a component extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The extension’s name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + +## The x-coordinate where the extension is placed. +@export var x: int: + set(val): + x = val + track_data(&"x", val) + +## The y-coordinate where the extension is placed. +@export var y: int: + set(val): + y = val + track_data(&"y", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionComponent: + var twitch_user_extension_component: TwitchUserExtensionComponent = TwitchUserExtensionComponent.new() + twitch_user_extension_component.active = _active + return twitch_user_extension_component + + +static func from_json(d: Dictionary) -> TwitchUserExtensionComponent: + var result: TwitchUserExtensionComponent = TwitchUserExtensionComponent.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("name", null) != null: + result.name = d["name"] + if d.get("x", null) != null: + result.x = d["x"] + if d.get("y", null) != null: + result.y = d["y"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_component.gd.uid b/addons/twitcher/generated/twitch_user_extension_component.gd.uid new file mode 100644 index 00000000..35d5bd3c --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_component.gd.uid @@ -0,0 +1 @@ +uid://cddfggl11jud5 diff --git a/addons/twitcher/generated/twitch_user_extension_component_update.gd b/addons/twitcher/generated/twitch_user_extension_component_update.gd new file mode 100644 index 00000000..25cd2b14 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_component_update.gd @@ -0,0 +1,61 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionComponentUpdate +class_name TwitchUserExtensionComponentUpdate + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured a component extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The x-coordinate where the extension is placed. +@export var x: int: + set(val): + x = val + track_data(&"x", val) + +## The y-coordinate where the extension is placed. +@export var y: int: + set(val): + y = val + track_data(&"y", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionComponentUpdate: + var twitch_user_extension_component_update: TwitchUserExtensionComponentUpdate = TwitchUserExtensionComponentUpdate.new() + twitch_user_extension_component_update.active = _active + return twitch_user_extension_component_update + + +static func from_json(d: Dictionary) -> TwitchUserExtensionComponentUpdate: + var result: TwitchUserExtensionComponentUpdate = TwitchUserExtensionComponentUpdate.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("x", null) != null: + result.x = d["x"] + if d.get("y", null) != null: + result.y = d["y"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_component_update.gd.uid b/addons/twitcher/generated/twitch_user_extension_component_update.gd.uid new file mode 100644 index 00000000..de1c7582 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_component_update.gd.uid @@ -0,0 +1 @@ +uid://yxljiyhoaroy diff --git a/addons/twitcher/generated/twitch_user_extension_overlay.gd b/addons/twitcher/generated/twitch_user_extension_overlay.gd new file mode 100644 index 00000000..58ba665d --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_overlay.gd @@ -0,0 +1,53 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionOverlay +class_name TwitchUserExtensionOverlay + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured an overlay extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The extension’s name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionOverlay: + var twitch_user_extension_overlay: TwitchUserExtensionOverlay = TwitchUserExtensionOverlay.new() + twitch_user_extension_overlay.active = _active + return twitch_user_extension_overlay + + +static func from_json(d: Dictionary) -> TwitchUserExtensionOverlay: + var result: TwitchUserExtensionOverlay = TwitchUserExtensionOverlay.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("name", null) != null: + result.name = d["name"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_overlay.gd.uid b/addons/twitcher/generated/twitch_user_extension_overlay.gd.uid new file mode 100644 index 00000000..b3fa869a --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_overlay.gd.uid @@ -0,0 +1 @@ +uid://bhn0il1dfql11 diff --git a/addons/twitcher/generated/twitch_user_extension_overlay_update.gd b/addons/twitcher/generated/twitch_user_extension_overlay_update.gd new file mode 100644 index 00000000..923e48bd --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_overlay_update.gd @@ -0,0 +1,45 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionOverlayUpdate +class_name TwitchUserExtensionOverlayUpdate + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured an overlay extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionOverlayUpdate: + var twitch_user_extension_overlay_update: TwitchUserExtensionOverlayUpdate = TwitchUserExtensionOverlayUpdate.new() + twitch_user_extension_overlay_update.active = _active + return twitch_user_extension_overlay_update + + +static func from_json(d: Dictionary) -> TwitchUserExtensionOverlayUpdate: + var result: TwitchUserExtensionOverlayUpdate = TwitchUserExtensionOverlayUpdate.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_overlay_update.gd.uid b/addons/twitcher/generated/twitch_user_extension_overlay_update.gd.uid new file mode 100644 index 00000000..d1764190 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_overlay_update.gd.uid @@ -0,0 +1 @@ +uid://ctorwljjiqhuv diff --git a/addons/twitcher/generated/twitch_user_extension_panel.gd b/addons/twitcher/generated/twitch_user_extension_panel.gd new file mode 100644 index 00000000..dafcbd80 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_panel.gd @@ -0,0 +1,53 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionPanel +class_name TwitchUserExtensionPanel + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured a panel extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + +## The extension’s name. +@export var name: String: + set(val): + name = val + track_data(&"name", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionPanel: + var twitch_user_extension_panel: TwitchUserExtensionPanel = TwitchUserExtensionPanel.new() + twitch_user_extension_panel.active = _active + return twitch_user_extension_panel + + +static func from_json(d: Dictionary) -> TwitchUserExtensionPanel: + var result: TwitchUserExtensionPanel = TwitchUserExtensionPanel.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + if d.get("name", null) != null: + result.name = d["name"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_panel.gd.uid b/addons/twitcher/generated/twitch_user_extension_panel.gd.uid new file mode 100644 index 00000000..71415772 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_panel.gd.uid @@ -0,0 +1 @@ +uid://dh7nkkluvupgv diff --git a/addons/twitcher/generated/twitch_user_extension_panel_update.gd b/addons/twitcher/generated/twitch_user_extension_panel_update.gd new file mode 100644 index 00000000..f01ab362 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_panel_update.gd @@ -0,0 +1,45 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserExtensionPanelUpdate +class_name TwitchUserExtensionPanelUpdate + +## A Boolean value that determines the extension’s activation state. If **false**, the user has not configured a panel extension. +@export var active: bool: + set(val): + active = val + track_data(&"active", val) + +## An ID that identifies the extension. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The extension’s version. +@export var version: String: + set(val): + version = val + track_data(&"version", val) + + + +## Constructor with all required fields. +static func create(_active: bool) -> TwitchUserExtensionPanelUpdate: + var twitch_user_extension_panel_update: TwitchUserExtensionPanelUpdate = TwitchUserExtensionPanelUpdate.new() + twitch_user_extension_panel_update.active = _active + return twitch_user_extension_panel_update + + +static func from_json(d: Dictionary) -> TwitchUserExtensionPanelUpdate: + var result: TwitchUserExtensionPanelUpdate = TwitchUserExtensionPanelUpdate.new() + if d.get("active", null) != null: + result.active = d["active"] + if d.get("id", null) != null: + result.id = d["id"] + if d.get("version", null) != null: + result.version = d["version"] + return result diff --git a/addons/twitcher/generated/twitch_user_extension_panel_update.gd.uid b/addons/twitcher/generated/twitch_user_extension_panel_update.gd.uid new file mode 100644 index 00000000..85c09209 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_extension_panel_update.gd.uid @@ -0,0 +1 @@ +uid://0nwbos23q70c diff --git a/addons/twitcher/generated/twitch_user_moderator.gd b/addons/twitcher/generated/twitch_user_moderator.gd new file mode 100644 index 00000000..d511f08d --- /dev/null +++ b/addons/twitcher/generated/twitch_user_moderator.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserModerator +class_name TwitchUserModerator + +## The ID of the user that has permission to moderate the broadcaster’s channel. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_login: String, _user_name: String) -> TwitchUserModerator: + var twitch_user_moderator: TwitchUserModerator = TwitchUserModerator.new() + twitch_user_moderator.user_id = _user_id + twitch_user_moderator.user_login = _user_login + twitch_user_moderator.user_name = _user_name + return twitch_user_moderator + + +static func from_json(d: Dictionary) -> TwitchUserModerator: + var result: TwitchUserModerator = TwitchUserModerator.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + return result diff --git a/addons/twitcher/generated/twitch_user_moderator.gd.uid b/addons/twitcher/generated/twitch_user_moderator.gd.uid new file mode 100644 index 00000000..099e79ea --- /dev/null +++ b/addons/twitcher/generated/twitch_user_moderator.gd.uid @@ -0,0 +1 @@ +uid://d3hk3jq527llk diff --git a/addons/twitcher/generated/twitch_user_subscription.gd b/addons/twitcher/generated/twitch_user_subscription.gd new file mode 100644 index 00000000..41eaad5e --- /dev/null +++ b/addons/twitcher/generated/twitch_user_subscription.gd @@ -0,0 +1,93 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserSubscription +class_name TwitchUserSubscription + +## An ID that identifies the broadcaster. +@export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + +## The broadcaster’s login name. +@export var broadcaster_login: String: + set(val): + broadcaster_login = val + track_data(&"broadcaster_login", val) + +## The broadcaster’s display name. +@export var broadcaster_name: String: + set(val): + broadcaster_name = val + track_data(&"broadcaster_name", val) + +## The ID of the user that gifted the subscription. The object includes this field only if `is_gift` is **true**. +@export var gifter_id: String: + set(val): + gifter_id = val + track_data(&"gifter_id", val) + +## The gifter’s login name. The object includes this field only if `is_gift` is **true**. +@export var gifter_login: String: + set(val): + gifter_login = val + track_data(&"gifter_login", val) + +## The gifter’s display name. The object includes this field only if `is_gift` is **true**. +@export var gifter_name: String: + set(val): + gifter_name = val + track_data(&"gifter_name", val) + +## A Boolean value that determines whether the subscription is a gift subscription. Is **true** if the subscription was gifted. +@export var is_gift: bool: + set(val): + is_gift = val + track_data(&"is_gift", val) + +## The type of subscription. Possible values are: +## +## * 1000 — Tier 1 +## * 2000 — Tier 2 +## * 3000 — Tier 3 +@export var tier: String: + set(val): + tier = val + track_data(&"tier", val) + + + +## Constructor with all required fields. +static func create(_broadcaster_id: String, _broadcaster_login: String, _broadcaster_name: String, _is_gift: bool, _tier: String) -> TwitchUserSubscription: + var twitch_user_subscription: TwitchUserSubscription = TwitchUserSubscription.new() + twitch_user_subscription.broadcaster_id = _broadcaster_id + twitch_user_subscription.broadcaster_login = _broadcaster_login + twitch_user_subscription.broadcaster_name = _broadcaster_name + twitch_user_subscription.is_gift = _is_gift + twitch_user_subscription.tier = _tier + return twitch_user_subscription + + +static func from_json(d: Dictionary) -> TwitchUserSubscription: + var result: TwitchUserSubscription = TwitchUserSubscription.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("broadcaster_login", null) != null: + result.broadcaster_login = d["broadcaster_login"] + if d.get("broadcaster_name", null) != null: + result.broadcaster_name = d["broadcaster_name"] + if d.get("gifter_id", null) != null: + result.gifter_id = d["gifter_id"] + if d.get("gifter_login", null) != null: + result.gifter_login = d["gifter_login"] + if d.get("gifter_name", null) != null: + result.gifter_name = d["gifter_name"] + if d.get("is_gift", null) != null: + result.is_gift = d["is_gift"] + if d.get("tier", null) != null: + result.tier = d["tier"] + return result diff --git a/addons/twitcher/generated/twitch_user_subscription.gd.uid b/addons/twitcher/generated/twitch_user_subscription.gd.uid new file mode 100644 index 00000000..2044a404 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_subscription.gd.uid @@ -0,0 +1 @@ +uid://ddpepdw7sx838 diff --git a/addons/twitcher/generated/twitch_user_vip.gd b/addons/twitcher/generated/twitch_user_vip.gd new file mode 100644 index 00000000..290257e7 --- /dev/null +++ b/addons/twitcher/generated/twitch_user_vip.gd @@ -0,0 +1,47 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/UserVip +class_name TwitchUserVip + +## An ID that uniquely identifies the VIP user. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The user’s display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The user’s login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + + + +## Constructor with all required fields. +static func create(_user_id: String, _user_name: String, _user_login: String) -> TwitchUserVip: + var twitch_user_vip: TwitchUserVip = TwitchUserVip.new() + twitch_user_vip.user_id = _user_id + twitch_user_vip.user_name = _user_name + twitch_user_vip.user_login = _user_login + return twitch_user_vip + + +static func from_json(d: Dictionary) -> TwitchUserVip: + var result: TwitchUserVip = TwitchUserVip.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + return result diff --git a/addons/twitcher/generated/twitch_user_vip.gd.uid b/addons/twitcher/generated/twitch_user_vip.gd.uid new file mode 100644 index 00000000..969014cc --- /dev/null +++ b/addons/twitcher/generated/twitch_user_vip.gd.uid @@ -0,0 +1 @@ +uid://ej4brk0redqo diff --git a/addons/twitcher/generated/twitch_video.gd b/addons/twitcher/generated/twitch_video.gd new file mode 100644 index 00000000..01b54e8b --- /dev/null +++ b/addons/twitcher/generated/twitch_video.gd @@ -0,0 +1,215 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +## +## #/components/schemas/Video +class_name TwitchVideo + +## An ID that identifies the video. +@export var id: String: + set(val): + id = val + track_data(&"id", val) + +## The ID of the stream that the video originated from if the video's type is "archive;" otherwise, **null**. +@export var stream_id: String: + set(val): + stream_id = val + track_data(&"stream_id", val) + +## The ID of the broadcaster that owns the video. +@export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + +## The broadcaster's login name. +@export var user_login: String: + set(val): + user_login = val + track_data(&"user_login", val) + +## The broadcaster's display name. +@export var user_name: String: + set(val): + user_name = val + track_data(&"user_name", val) + +## The video's title. +@export var title: String: + set(val): + title = val + track_data(&"title", val) + +## The video's description. +@export var description: String: + set(val): + description = val + track_data(&"description", val) + +## The date and time, in UTC, of when the video was created. The timestamp is in RFC3339 format. +@export var created_at: String: + set(val): + created_at = val + track_data(&"created_at", val) + +## The date and time, in UTC, of when the video was published. The timestamp is in RFC3339 format. +@export var published_at: String: + set(val): + published_at = val + track_data(&"published_at", val) + +## The video's URL. +@export var url: String: + set(val): + url = val + track_data(&"url", val) + +## A URL to a thumbnail image of the video. Before using the URL, you must replace the `%{width}` and `%{height}` placeholders with the width and height of the thumbnail you want returned. Due to current limitations, `${width}` must be 320 and `${height}` must be 180. +@export var thumbnail_url: String: + set(val): + thumbnail_url = val + track_data(&"thumbnail_url", val) + +## The video's viewable state. Always set to **public**. +@export var viewable: String: + set(val): + viewable = val + track_data(&"viewable", val) + +## The number of times that users have watched the video. +@export var view_count: int: + set(val): + view_count = val + track_data(&"view_count", val) + +## The ISO 639-1 two-letter language code that the video was broadcast in. For example, the language code is DE if the video was broadcast in German. For a list of supported languages, see [Supported Stream Language](https://help.twitch.tv/s/article/languages-on-twitch#streamlang). The language value is "other" if the video was broadcast in a language not in the list of supported languages. +@export var language: String: + set(val): + language = val + track_data(&"language", val) + +## The video's type. Possible values are: +## +## * archive — An on-demand video (VOD) of one of the broadcaster's past streams. +## * highlight — A highlight reel of one of the broadcaster's past streams. See [Creating Highlights](https://help.twitch.tv/s/article/creating-highlights-and-stream-markers). +## * upload — A video that the broadcaster uploaded to their video library. See Upload under [Video Producer](https://help.twitch.tv/s/article/video-on-demand?language=en%5FUS#videoproducer). +@export var type: String: + set(val): + type = val + track_data(&"type", val) + +## The video's length in ISO 8601 duration format. For example, 3m21s represents 3 minutes, 21 seconds. +@export var duration: String: + set(val): + duration = val + track_data(&"duration", val) + +## The segments that Twitch Audio Recognition muted; otherwise, **null**. +@export var muted_segments: Array[MutedSegments]: + set(val): + muted_segments = val + track_data(&"muted_segments", val) + + + +## Constructor with all required fields. +static func create(_id: String, _stream_id: String, _user_id: String, _user_login: String, _user_name: String, _title: String, _description: String, _created_at: String, _published_at: String, _url: String, _thumbnail_url: String, _viewable: String, _view_count: int, _language: String, _type: String, _duration: String, _muted_segments: Array[MutedSegments]) -> TwitchVideo: + var twitch_video: TwitchVideo = TwitchVideo.new() + twitch_video.id = _id + twitch_video.stream_id = _stream_id + twitch_video.user_id = _user_id + twitch_video.user_login = _user_login + twitch_video.user_name = _user_name + twitch_video.title = _title + twitch_video.description = _description + twitch_video.created_at = _created_at + twitch_video.published_at = _published_at + twitch_video.url = _url + twitch_video.thumbnail_url = _thumbnail_url + twitch_video.viewable = _viewable + twitch_video.view_count = _view_count + twitch_video.language = _language + twitch_video.type = _type + twitch_video.duration = _duration + twitch_video.muted_segments = _muted_segments + return twitch_video + + +static func from_json(d: Dictionary) -> TwitchVideo: + var result: TwitchVideo = TwitchVideo.new() + if d.get("id", null) != null: + result.id = d["id"] + if d.get("stream_id", null) != null: + result.stream_id = d["stream_id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("user_login", null) != null: + result.user_login = d["user_login"] + if d.get("user_name", null) != null: + result.user_name = d["user_name"] + if d.get("title", null) != null: + result.title = d["title"] + if d.get("description", null) != null: + result.description = d["description"] + if d.get("created_at", null) != null: + result.created_at = d["created_at"] + if d.get("published_at", null) != null: + result.published_at = d["published_at"] + if d.get("url", null) != null: + result.url = d["url"] + if d.get("thumbnail_url", null) != null: + result.thumbnail_url = d["thumbnail_url"] + if d.get("viewable", null) != null: + result.viewable = d["viewable"] + if d.get("view_count", null) != null: + result.view_count = d["view_count"] + if d.get("language", null) != null: + result.language = d["language"] + if d.get("type", null) != null: + result.type = d["type"] + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("muted_segments", null) != null: + for value in d["muted_segments"]: + result.muted_segments.append(MutedSegments.from_json(value)) + return result + + + +## The segments that Twitch Audio Recognition muted; otherwise, **null**. +## #/components/schemas/Video/MutedSegments +class MutedSegments extends TwitchData: + + ## The duration of the muted segment, in seconds. + @export var duration: int: + set(val): + duration = val + track_data(&"duration", val) + + ## The offset, in seconds, from the beginning of the video to where the muted segment begins. + @export var offset: int: + set(val): + offset = val + track_data(&"offset", val) + + + + ## Constructor with all required fields. + static func create(_duration: int, _offset: int) -> MutedSegments: + var muted_segments: MutedSegments = MutedSegments.new() + muted_segments.duration = _duration + muted_segments.offset = _offset + return muted_segments + + + static func from_json(d: Dictionary) -> MutedSegments: + var result: MutedSegments = MutedSegments.new() + if d.get("duration", null) != null: + result.duration = d["duration"] + if d.get("offset", null) != null: + result.offset = d["offset"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_video.gd.uid b/addons/twitcher/generated/twitch_video.gd.uid new file mode 100644 index 00000000..aed57727 --- /dev/null +++ b/addons/twitcher/generated/twitch_video.gd.uid @@ -0,0 +1 @@ +uid://bxdf1uui8mwhr diff --git a/addons/twitcher/generated/twitch_warn_chat_user.gd b/addons/twitcher/generated/twitch_warn_chat_user.gd new file mode 100644 index 00000000..1762244a --- /dev/null +++ b/addons/twitcher/generated/twitch_warn_chat_user.gd @@ -0,0 +1,152 @@ +@tool +extends TwitchData + +# CLASS GOT AUTOGENERATED DON'T CHANGE MANUALLY. CHANGES CAN BE OVERWRITTEN EASILY. + +class_name TwitchWarnChatUser + + + +## +## #/components/schemas/WarnChatUserBody +class Body extends TwitchData: + + ## A list that contains information about the warning. + @export var data: BodyData: + set(val): + data = val + track_data(&"data", val) + + + + ## Constructor with all required fields. + static func create(_data: BodyData) -> Body: + var body: Body = Body.new() + body.data = _data + return body + + + static func from_json(d: Dictionary) -> Body: + var result: Body = Body.new() + if d.get("data", null) != null: + result.data = BodyData.from_json(d["data"]) + return result + + + +## A list that contains information about the warning. +## #/components/schemas/WarnChatUserBody/Data +class BodyData extends TwitchData: + + ## The ID of the twitch user to be warned. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## A custom reason for the warning. **Max 500 chars.** + @export var reason: String: + set(val): + reason = val + track_data(&"reason", val) + + + + ## Constructor with all required fields. + static func create(_user_id: String, _reason: String) -> BodyData: + var body_data: BodyData = BodyData.new() + body_data.user_id = _user_id + body_data.reason = _reason + return body_data + + + static func from_json(d: Dictionary) -> BodyData: + var result: BodyData = BodyData.new() + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("reason", null) != null: + result.reason = d["reason"] + return result + + + +## +## #/components/schemas/WarnChatUserResponse +class Response extends TwitchData: + + ## A list that contains information about the warning. + @export var data: Array[ResponseData]: + set(val): + data = val + track_data(&"data", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_data: Array[ResponseData]) -> Response: + var response: Response = Response.new() + response.data = _data + return response + + + static func from_json(d: Dictionary) -> Response: + var result: Response = Response.new() + if d.get("data", null) != null: + for value in d["data"]: + result.data.append(ResponseData.from_json(value)) + return result + + + +## A list that contains information about the warning. +## #/components/schemas/WarnChatUserResponse/Data +class ResponseData extends TwitchData: + + ## The ID of the channel in which the warning will take effect. + @export var broadcaster_id: String: + set(val): + broadcaster_id = val + track_data(&"broadcaster_id", val) + + ## The ID of the warned user. + @export var user_id: String: + set(val): + user_id = val + track_data(&"user_id", val) + + ## The ID of the user who applied the warning. + @export var moderator_id: String: + set(val): + moderator_id = val + track_data(&"moderator_id", val) + + ## The reason provided for warning. + @export var reason: String: + set(val): + reason = val + track_data(&"reason", val) + var response: BufferedHTTPClient.ResponseData + + + ## Constructor with all required fields. + static func create(_broadcaster_id: String, _user_id: String, _moderator_id: String, _reason: String) -> ResponseData: + var response_data: ResponseData = ResponseData.new() + response_data.broadcaster_id = _broadcaster_id + response_data.user_id = _user_id + response_data.moderator_id = _moderator_id + response_data.reason = _reason + return response_data + + + static func from_json(d: Dictionary) -> ResponseData: + var result: ResponseData = ResponseData.new() + if d.get("broadcaster_id", null) != null: + result.broadcaster_id = d["broadcaster_id"] + if d.get("user_id", null) != null: + result.user_id = d["user_id"] + if d.get("moderator_id", null) != null: + result.moderator_id = d["moderator_id"] + if d.get("reason", null) != null: + result.reason = d["reason"] + return result + \ No newline at end of file diff --git a/addons/twitcher/generated/twitch_warn_chat_user.gd.uid b/addons/twitcher/generated/twitch_warn_chat_user.gd.uid new file mode 100644 index 00000000..89e88659 --- /dev/null +++ b/addons/twitcher/generated/twitch_warn_chat_user.gd.uid @@ -0,0 +1 @@ +uid://s0ewsss5pv4q diff --git a/addons/twitcher/irc/twitch_irc.gd b/addons/twitcher/irc/twitch_irc.gd new file mode 100644 index 00000000..a255101d --- /dev/null +++ b/addons/twitcher/irc/twitch_irc.gd @@ -0,0 +1,467 @@ +@icon("res://addons/twitcher/assets/chat-icon.svg") +@tool +extends Twitcher + +## @deprecated: Twitch plans to remove IRC so use the eventsub solution instead +class_name TwitchIRC + +static var log: TwitchLogger = TwitchLogger.new("TwitchIRC") + +## Sent when the bot or moderator removes all messages from the chat room or removes all messages for the specified user. +signal received_clearchat(channel_name: String, banned_or_timeout_user: String, tags: TwitchTags.ClearChat) +## Sent when the bot removes a single message from the chat room. +signal received_clearmsg(channel_name: String, chat_message_removed: String, tags: TwitchTags.ClearMsg) +## Sent after the bot authenticates with the server. +signal received_global_userstate(tags: TwitchTags.GlobalUserState) +## Sent when a channel starts or stops hosting viewers from another channel. +signal received_host_target(channel_being_hosted: String, hosting_channel: String, number_of_viewers: int) +## Sent to indicate the outcome of an action like banning a user.[br] +## handled = when the Twitcher already handled the message. +signal received_notice(channel_name: String, message: String, tags: TwitchTags.Notice, handled: bool) +## Sent when the Twitch IRC server needs to terminate the connection for maintenance reasons. This gives your bot a chance to perform minimal clean up and save state before the server terminates the connection. The amount of time between receiving the message and the server closing the connection is indeterminate. +signal received_reconnect +## Sent when the bot joins a channel or when the channel’s chat settings change. +signal received_roomstate(channel_name: String, tags: TwitchTags.Roomstate) +## Sent when events like someone subscribing to the channel occurs. [br] +## - A user subscribes to the channel, re-subscribes to the channel, or gifts a subscription to another user.[br] +## - Another broadcaster raids the channel. Raid is a Twitch feature that lets broadcasters send their viewers to another channel to help support and grow other members in the community.[br] +## - A viewer milestone is celebrated such as a new viewer chatting for the first time. +signal received_usernotice(channel_name: String, message: String, tags: TwitchTags.Usernotice) +## Sent when the bot joins a channel or sends a PRIVMSG message. +signal received_userstate(channel_name: String, tags: TwitchTags.Userstate) +## Sent when a WHISPER message is directed specifically to your bot. Your bot will never receive whispers sent to other users.[br] +## from_user - The user that’s sending the whisper message.[br] +## to_user - The user that’s receiving the whisper message. +signal received_whisper(from_user: String, to_user: String, message: String, tags: TwitchTags.Whisper) +## The Twitch IRC server sends this message after a user posts a message to the chat room. +signal received_privmsg(channel_name: String, username: String, message: String, tags: TwitchTags.PrivMsg) +## When the token isn't valid anymore +signal unauthenticated +## When the token doesn't have enough permissions to join IRC +signal unauthorized +## Called when the connection got opened and the authorization was done +signal connection_opened +## Called when the connection got closed +signal connection_closed + + +enum JoinState { + NOT_JOINED, JOINING, JOINED +} + +class ChannelData extends RefCounted: + + signal has_joined + + var join_state: JoinState = JoinState.NOT_JOINED + var channel_name: String + var nodes: Array[TwitchIrcChannel] = [] + var user_state: TwitchTags.Userstate + var room_state: TwitchTags.Roomstate: + set(val): + room_state = val + if join_state != JoinState.JOINED && val != null: + join_state = JoinState.JOINED + has_joined.emit() + + func _init(channel: String) -> void: + channel_name = channel + + func leave() -> void: + room_state = null + join_state = JoinState.NOT_JOINED + for node in nodes: node.leave() + + func is_joined() -> void: + if join_state != JoinState.JOINED: await has_joined + + +class ParsedMessage extends RefCounted: + ## Parses all of the messages of IRC + ## Group1: Tags + ## Group2: Server / To User (Whisper) / From User (Chat) + ## Group3: Command + ## Group4: Channel / From User (Whisper) + ## Group5: Message / Payload + var _irc_message_parser_regex = RegEx.create_from_string("(@.*? )?:(.*?)( [A-Z0-9]*)( #?.*?)?( :.*?)?$") + + var tags: String: + get: return tags.trim_prefix("@") + + ## Server / To User (Whisper) / From User (Chat) + var server: String + + var command: String: + get: return command.strip_edges() + + ## Channel / From User (Whisper) + var channel: String: + get: return channel.strip_edges().trim_prefix("#") + + ## Message / Payload + var message: String: + get: return message.strip_edges().trim_prefix(":") + + func _init(msg: String) -> void: + var matches = _irc_message_parser_regex.search(msg) + if matches != null: + tags = matches.get_string(1) + server = matches.get_string(2) + command = matches.get_string(3) + channel = matches.get_string(4) + message = matches.get_string(5) + + +class EmoteLocation extends RefCounted: + var id : Variant + var start : int + var end : int + var sprite_frames: SpriteFrames + + func _init(emote_id: Variant, start_idx: int, end_idx: int): + self.id = emote_id + self.start = start_idx + self.end = end_idx + + static func smaller(a : EmoteLocation, b : EmoteLocation): + return a.start < b.start + + +@export var setting: TwitchIrcSetting = TwitchIrcSetting.new(): + set(val): + setting = val + _client.connection_url = setting.server + update_configuration_warnings() +@export var token: OAuthToken: + set(val): + token = val + update_configuration_warnings() +@export var irc_send_message_delay: int = 360 +## All connected channels of the bot. +## Key: channel_name as StringName | Value: ChannelData +var _channels := {} + +## will automatically reconnect in case of authorization problems +var _auto_reconnect: bool +var _ready_to_send: bool +var _client := WebsocketClient.new() + +## Timestamp when the next message should be sent. +var _next_message := Time.get_ticks_msec() + +## Messages to send with an interval for disconnection protection +## see TwitchIrcSetting.send_message_delay_ms. +var _chat_queue : Array[String] = [] + + +func _init() -> void: + _client.name = "Websocket" + _client.message_received.connect(_data_received) + _client.connection_established.connect(_on_connection_established) + _client.connection_closed.connect(_on_connection_closed) + + +func _ready() -> void: + token.authorized.connect(_on_authorized) + add_child(_client) + + +func _on_authorized() -> void: + log.i("Token got authorized reconnect to irc? Client Closed: %s, Auto Reconnect enabled: %s" % [_client.is_closed, _client.auto_reconnect]) + if _client.is_closed and _auto_reconnect: + open_connection() + + +## Propergated call from TwitchService +func do_setup() -> void: + await open_connection() + log.i("IRC setup") + + +## Propergated call from TwitchService +func do_unsetup() -> void: + close_connection() + log.i("IRC unsetup") + + +func open_connection() -> void: + _auto_reconnect = true + log.i("Irc open connection") + await _client.open_connection() + + +func close_connection() -> void: + _auto_reconnect = false + _client.close(1000, "intentionally closed") + log.i("Irc closed connection") + + +func _on_connection_established() -> void: + _login() + _reconnect_to_channels() + connection_opened.emit() + + +func _on_connection_closed() -> void: + _ready_to_send = false + connection_closed.emit() + for channel_name: StringName in _channels: + _channels[channel_name].leave() + + +func _process(delta: float) -> void: + if _ready_to_send: _send_messages() + + +## Sends the login message for authorization pupose and sets an username +func _login() -> void: + _client.send_text("PASS oauth:%s" % await token.get_access_token()) + _send("NICK " + setting.username) + _send("CAP REQ :" + " ".join(setting.irc_capabilities)) + + +## Reconnect to all channels the bot was joined before (in case programatically joined channels) +func _reconnect_to_channels(): + for channel_name in _channels: join_channel(channel_name) + + +func _join_channels_on_connect(): + for channel_name: StringName in setting.auto_join_channels: + var channel = join_channel(channel_name) + await channel.is_joined() + log.i("%s joined" % channel_name) + + +## Receives data on the websocket aka new messages +func _data_received(data : PackedByteArray) -> void: + var messages : PackedStringArray = data.get_string_from_utf8().strip_edges(false).split("\r\n") + for message: String in messages: + # Reminder PONG messages is just different cant use match for it... + if message.begins_with("PING"): + _send("PONG :tmi.twitch.tv") + continue + + var parsed_message = ParsedMessage.new(message) + _handle_message(parsed_message) + + +## Tries to send messages as long as the websocket is open +func _send_messages() -> void: + if _chat_queue.is_empty(): + return + + if not _client.is_open: + log.e("Can't send message. Connection not open.") + # Maybe buggy when the websocket got opened but not authorized yet + # Can possible happen when we have a lot of load and a reconnect in the socket + return + + if _next_message <= Time.get_ticks_msec(): + var msg_to_send = _chat_queue.pop_front() + _send(msg_to_send) + _next_message = Time.get_ticks_msec() + irc_send_message_delay + + +## Sends join channel message +func join_channel(channel_name : StringName) -> ChannelData: + var lower_channel = channel_name.to_lower() + if not _channels.has(channel_name): + _channels[channel_name] = ChannelData.new(lower_channel) + + if _channels[channel_name].join_state == JoinState.NOT_JOINED: + _chat_queue.append("JOIN #" + lower_channel) + _channels[channel_name].join_state = JoinState.JOINING + + return _channels[channel_name] + + +## Sends leave channel message +func leave_channel(channel_name : StringName) -> void: + if not _channels.has(channel_name): + log.e("Can't leave %s channel cause we are not joined" % channel_name) + return + + var lower_channel : StringName = channel_name.to_lower() + _chat_queue.append("PART #" + lower_channel) + _channels.erase(lower_channel) + + +## Sends a chat message to a channel. Defaults to the only connected channel. +## Channel should be always without '#'. +func chat(message : String, channel_name : StringName = &""): + var channel_names : Array = _channels.keys() + if channel_name == &"" && channel_names.size() == 1: + channel_name = channel_names[0] + + if channel_name == &"": + log.e("No channel is specified to send %s" % message) + return + + _chat_queue.append("PRIVMSG #%s :%s\r\n" % [channel_name, message]) + + # Call it defered otherwise the response of the bot will be send before the command. + _send_message_to_channel.call_deferred(channel_name, message) + + +## send the message of the bot to the channel for display purpose +func _send_message_to_channel(channel_name: StringName, message: String) -> void: + if _channels.has(channel_name): + var channel = _channels[channel_name] as ChannelData + var username = channel.user_state.display_name + # Convert the tags in a dirty way + var tag = TwitchTags.PrivMsg.new(channel.user_state._raw) + tag.room_id = channel.room_state.room_id + received_privmsg.emit(channel_name, username, message, tag) + + +## Sends a string message to Twitch. +func _send(text : String) -> void: + _client.send_text(text) + log.i("< " + text.strip_edges(false)) + + +## Handles all the messages. Tags can be empty when not requested via capabilities +func _handle_message(parsed_message : ParsedMessage) -> void: + if parsed_message.command != "WHISPER": + log.i("> [%15s] %s: %s" % [parsed_message.command, parsed_message.server, parsed_message.message]) + + match parsed_message.command: + "001": + log.i("Authentication successful.") + _join_channels_on_connect() + _ready_to_send = true + + "CLEARCHAT": + var clear_chat_tags = TwitchTags.ClearChat.new(parsed_message.tags) + var user_to_ban_or_timeout = parsed_message.message + received_clearchat.emit(parsed_message.channel, parsed_message.message, clear_chat_tags) + + "CLEARMSG": + var clear_msg_tags = TwitchTags.ClearMsg.new(parsed_message.tags) + var message_to_remove = parsed_message.message + received_clearmsg.emit(parsed_message.channel, message_to_remove, clear_msg_tags) + + "GLOBALUSERSTATE": + var global_userstate = TwitchTags.GlobalUserState.new(parsed_message.tags) + received_global_userstate.emit(global_userstate) + + "HOSTTARGET": + # Example: [-|] + var host_target_message = parsed_message.message.split(" ") + var channel_being_hosted = host_target_message[0] + var number_of_viewers = int(host_target_message[1]) + var hosting_channel = parsed_message.channel + received_host_target.emit(channel_being_hosted, hosting_channel, number_of_viewers) + + "NOTICE": + var notice_tags = TwitchTags.Notice.new(parsed_message.tags) + var message = parsed_message.message + var handled := false + if not await _handle_cmd_notice(message): + handled = true + received_notice.emit(parsed_message.channel, message, notice_tags, handled) + + "RECONNECT": + received_reconnect.emit() + + "ROOMSTATE": + var roomstate_tags = TwitchTags.Roomstate.new(parsed_message.tags) + var channel_name = parsed_message.channel + received_roomstate.emit(channel_name, roomstate_tags) + + var channel = _channels[channel_name] as ChannelData + channel.room_state = roomstate_tags + + "USERNOTICE": + var user_notice_tags = TwitchTags.Usernotice.new(parsed_message.tags) + received_usernotice.emit(parsed_message.channel, parsed_message.message, user_notice_tags) + + "USERSTATE": + var userstate_tags = TwitchTags.Userstate.new(parsed_message.tags) + var channel_name = parsed_message.channel + received_usernotice.emit(channel_name, userstate_tags) + + var channel = _channels[channel_name] as ChannelData + channel.user_state = userstate_tags + + "WHISPER": + var whisper_tags = TwitchTags.Whisper.new(parsed_message.tags) + # Example: :!@.tmi.twitch.tv + var to_user = parsed_message.server + to_user = to_user.substr(0, to_user.find("!")) + # Special case for whisper + var from_user = parsed_message.channel + received_whisper.emit(from_user, to_user, parsed_message.message, whisper_tags) + + "PRIVMSG": + var privmsg_tags = TwitchTags.PrivMsg.new(parsed_message.tags) + var from_user = parsed_message.server + from_user = from_user.substr(0, from_user.find("!")) + received_privmsg.emit(parsed_message.channel, from_user, parsed_message.message, privmsg_tags) + + +## Handles the update of rooms when joining the channel or a moderator +## updates it (Example :tmi.twitch.tv ROOMSTATE #bar) +func _handle_cmd_state(command: String, channel_name: StringName, tags: Dictionary) -> void: + # right(-1) -> Remove the preceding # of the channel name + channel_name = channel_name.right(-1).to_lower() + if not _channels.has(channel_name): + _channels[channel_name] = _create_channel(channel_name) + + var channel: TwitchIrcChannel = _channels[channel_name] + channel.update_state(command, tags) + #channel_data_updated.emit(channel_name, channel.data) + log.i("Channel updated %s" % channel_name) + + +func _create_channel(channel_name: StringName) -> TwitchIrcChannel: + var channel = TwitchIrcChannel.new() + channel.channel_name = channel_name + _channels[channel_name] = channel + Engine.get_main_loop().root.add_child(channel) + return channel + + +## Tracks the channel. +func add_channel(channel: TwitchIrcChannel): + var channel_name = channel.channel_name + if not _channels.has(channel_name): + join_channel(channel_name) + var nodes = _channels[channel_name].nodes as Array[TwitchIrcChannel] + nodes.append(channel) + + +## Remove the channel from getting tracked within the service +func remove_channel(channel: TwitchIrcChannel): + var channel_name = channel.channel_name + var channel_data = _channels[channel_name] as ChannelData + channel_data.nodes.erase(channel) + if channel_data.nodes.is_empty(): + leave_channel(channel_name) + + +func _handle_cmd_notice(info: String) -> bool: + if info == "Login authentication failed" || info == "Login unsuccessful": + log.e("Authentication failed.") + unauthenticated.emit() + _client.close(1000, "Unauthenticated.") + return true + elif info == "You don't have permission to perform that action": + log.i("No permission. Please check if you have all required scopes (chat:read or chat:write).") + unauthorized.emit() + _client.close(1000, "Token became invalid.") + return true + return false + + +func get_client() -> WebsocketClient: + return _client + + +func _get_configuration_warnings() -> PackedStringArray: + var result: Array[String] = [] + if token == null: + result.append("Token is missing") + if setting == null: + result.append("IRC Settings are missing") + return result diff --git a/addons/twitcher/irc/twitch_irc.gd.uid b/addons/twitcher/irc/twitch_irc.gd.uid new file mode 100644 index 00000000..a0d8c1a1 --- /dev/null +++ b/addons/twitcher/irc/twitch_irc.gd.uid @@ -0,0 +1 @@ +uid://wkh0l2xsyapj diff --git a/addons/twitcher/irc/twitch_irc_channel.gd b/addons/twitcher/irc/twitch_irc_channel.gd new file mode 100644 index 00000000..3d5ff4ba --- /dev/null +++ b/addons/twitcher/irc/twitch_irc_channel.gd @@ -0,0 +1,110 @@ +@icon("res://addons/twitcher/assets/chat-icon.svg") +extends Twitcher + +## Direct access to the chat for one specific channel +## +## Usefull when using multiple channels otherwise TwitchIRC has everything you need +## This one exists only for tracking user join and leave events. +## ## @deprecated: Twitch plans to remove IRC so use the eventsub solution instead +class_name TwitchIrcChannel + +static var _log: TwitchLogger = TwitchLogger.new("TwitchIrcChannel") + +## when a chat message in this channel got received +signal message_received(from_user: String, message: String, tags: TwitchTags.Message) + +## Sent when the bot joins a channel or sends a PRIVMSG message. +signal user_state_received(tags: TwitchTags.Userstate) + +## Sent when the bot joins a channel or when the channel’s chat settings change. +signal room_state_received(tags: TwitchTags.Roomstate) + +## Called when the bot joined the channel or atleast get the channel informations. +signal has_joined() + +## Called when thie bot left the channel. +signal has_left() + +@export var twitch_service: TwitchService +@export var channel_name: StringName: + set = _update_channel_name, + get = _get_channel_name + +var user_state: TwitchTags.Userstate +var room_state: TwitchTags.Roomstate: + set(val): + room_state = val + if !joined && val != null: + joined = true + has_joined.emit() + +var joined: bool +var irc: TwitchIRC + + +func _enter_tree() -> void: + _enter_channel() + + +func _get_channel_name() -> StringName: + return channel_name.to_lower() + + +func _update_channel_name(new_name: StringName) -> void: + if channel_name != "": irc.remove_channel(self) + channel_name = new_name + _enter_channel() + + +func _enter_channel() -> void: + if irc == null: return + if channel_name == &"": + _log.e("No channel is specified to join. The channel name can be set on the TwitchIrcChannel node.") + return + irc.add_channel(self) + + +func _ready() -> void: + irc = twitch_service.irc + irc.received_privmsg.connect(_on_message_received) + irc.received_roomstate.connect(_on_roomstate_received) + irc.received_userstate.connect(_on_userstate_received) + _enter_channel() + + +func _exit_tree() -> void: + irc.remove_channel(self) + + +func _on_message_received(channel: StringName, from_user: String, message: String, tags: TwitchTags.PrivMsg): + if channel_name != channel: return + var message_tag = TwitchTags.Message.from_priv_msg(tags) + await message_tag.load_sprites(twitch_service) + message_received.emit(from_user, message, message_tag) + + +func _on_roomstate_received(channel: StringName, tags: TwitchTags.Roomstate): + if channel != channel_name: return + room_state = tags + room_state_received.emit(room_state) + + +func _on_userstate_received(channel: StringName, tags: TwitchTags.Userstate): + if channel != channel_name: return + user_state = tags + user_state_received.emit(user_state) + + +func chat(message: String) -> void: + await is_joined() + irc.chat(message, channel_name) + + +func is_joined() -> void: + if not joined: await has_joined + + +func leave() -> void: + room_state = null + joined = false + has_left.emit() diff --git a/addons/twitcher/irc/twitch_irc_channel.gd.uid b/addons/twitcher/irc/twitch_irc_channel.gd.uid new file mode 100644 index 00000000..2ca03d40 --- /dev/null +++ b/addons/twitcher/irc/twitch_irc_channel.gd.uid @@ -0,0 +1 @@ +uid://cva5e053boj4u diff --git a/addons/twitcher/irc/twitch_irc_setting.gd b/addons/twitcher/irc/twitch_irc_setting.gd new file mode 100644 index 00000000..09eae0ca --- /dev/null +++ b/addons/twitcher/irc/twitch_irc_setting.gd @@ -0,0 +1,38 @@ +extends Resource + +## @deprecated: Twitch plans to remove IRC so use the eventsub solution instead +class_name TwitchIrcSetting + +const CAP_COMMANDS := &"twitch.tv/commands" +const CAP_MEMBERSHIP := &"twitch.tv/membership" +const CAP_TAGS := &"twitch.tv/tags" + + +## The name of the bot within the chat +@export var username := "" + +## Join the channels after connect +@export var auto_join_channels: Array[StringName] = [] + +## Twitch IRC Server URL +@export var server := "wss://irc-ws.chat.twitch.tv:443" + +## Needed because IRC may disconnect on to many message per second +@export var send_message_delay_ms := 320 + +@export_flags(CAP_COMMANDS, CAP_MEMBERSHIP, CAP_TAGS) var capabilities := 0 + +var irc_capabilities: Array[StringName]: + get(): + var result : Array[StringName] = [] + if capabilities & 1 == 1: + result.append(CAP_COMMANDS) + if capabilities & 2 == 2: + result.append(CAP_MEMBERSHIP) + if capabilities & 4 == 4: + result.append(CAP_TAGS) + return result + + +static func get_all_capabillities() -> Array[StringName]: + return [CAP_COMMANDS, CAP_MEMBERSHIP, CAP_TAGS]; diff --git a/addons/twitcher/irc/twitch_irc_setting.gd.uid b/addons/twitcher/irc/twitch_irc_setting.gd.uid new file mode 100644 index 00000000..eccc8d84 --- /dev/null +++ b/addons/twitcher/irc/twitch_irc_setting.gd.uid @@ -0,0 +1 @@ +uid://c8mv0lq0a2l8f diff --git a/addons/twitcher/irc/twitch_tags.gd b/addons/twitcher/irc/twitch_tags.gd new file mode 100644 index 00000000..fc4ab20e --- /dev/null +++ b/addons/twitcher/irc/twitch_tags.gd @@ -0,0 +1,469 @@ +extends RefCounted + +class_name TwitchTags + +## A normal user +const USER_TYPE_NORMA := &"" +## A Twitch administrator +const USER_TYPE_ADMIN := &"admin" +## A global moderator +const USER_TYPE_GLOBAL_MOD := &"global_mod" +## A Twitch employee +const USER_TYPE_STAFF := &"staff" + +const MSG_ID_SUB := &"sub" +const MSG_ID_RESUB := &"resub" +const MSG_ID_SUBGIFT := &"subgift" +const MSG_ID_SUBMYSTERYGIFT := &"submysterygift" +const MSG_ID_GIFTPAIDUPGRADE := &"giftpaidupgrade" +const MSG_ID_REWARDGIFT := &"rewardgift" +const MSG_ID_ANONGIFTPAIDUPGRADE := &"anongiftpaidupgrade" +const MSG_ID_RAID := &"raid" +const MSG_ID_UNRAID := &"unraid" +const MSG_ID_RITUAL := &"ritual" +const MSG_ID_BITSBADGETIER := &"bitsbadgetier" +#region TagWrapper + +class Message extends RefCounted: + var color: String + var _badges: String + var _emotes: String + var room_id: String + var raw: Variant + + var badges: Array[SpriteFrames] + var emotes: Array[TwitchIRC.EmoteLocation] + + + static func from_priv_msg(tag: PrivMsg) -> Message: + var msg = Message.new() + msg.color = tag.color + msg._badges = tag.badges + msg._emotes = tag.emotes + msg.room_id = tag.room_id + msg.raw = tag + return msg + + + func load_sprites(twitch_service: TwitchService) -> void: + badges = await _load_badges(twitch_service) + emotes = await _load_emotes(twitch_service) + pass + + + func _load_badges(twitch_service: TwitchService) -> Array[SpriteFrames]: + var badge_definitions : Array[TwitchBadgeDefinition] = [] + for badge in _badges.split(",", false): + # Maybe Broke?! + var badge_info := badge.split("/") + var badge_definition = TwitchBadgeDefinition.new(badge_info[0], badge_info[1], 1, room_id) + badge_definitions.append(badge_definition) + var result = await twitch_service.media_loader.get_badges(badge_definitions) + var sprite_frames : Array[SpriteFrames] = [] + sprite_frames.assign(result.values()) + return sprite_frames + + + func _load_emotes(twitch_service: TwitchService) -> Array[TwitchIRC.EmoteLocation]: + var locations : Array[TwitchIRC.EmoteLocation] = [] + var emotes_to_load : Array[String] = [] + if _emotes != null && _emotes != "": + for emote in _emotes.split("/", false): + var data : Array = emote.split(":") + for d in data[1].split(","): + var start_end = d.split("-") + locations.append(TwitchIRC.EmoteLocation.new(data[0], int(start_end[0]), int(start_end[1]))) + emotes_to_load.append(data[0]) + locations.sort_custom(Callable(TwitchIRC.EmoteLocation, "smaller")) + + var emotes_definition: Dictionary = await twitch_service.media_loader.get_emotes(emotes_to_load) + for emote_location: TwitchIRC.EmoteLocation in locations: + emote_location.sprite_frames = emotes_definition[emote_location.id] + + return locations + + + + func get_color() -> String: + return color + +#endregion + +#region Lowlevel Tags + +class BaseTags: + var _raw: String + var _unmapped: Dictionary = {} + + + func parse_tags(tag_string: String, output: Object) -> void: + _raw = tag_string + if tag_string.left(1) == "@": + tag_string = tag_string.substr(1) + + var tags = tag_string.split(";") + for tag in tags: + if tag == "": continue + var tag_value = tag.split("=") + var property_name = tag_value[0].replace("-", "_") + if _has_property(output, property_name): + output.set(property_name, tag_value[1]) + elif tag_value.size() == 2: + output._unmapped[property_name] = tag_value[1] + else: + output._unmapped[property_name] = "" + + + func _has_property(obj: Object, property_name: String) -> bool: + var properties = obj.get_property_list() + for property in properties: + if property.name == property_name: + return true + return false + + + func get_unmapped(property: String) -> Variant: + return _unmapped[property] + + + func has_unmapped(property: String) -> bool: + return _unmapped.has(property) + +## Sent when the bot or moderator removes all messages from the chat room or removes all messages for the specified user. [br] +## @ban-duration=;room-id=;target-user-id=;tmi-sent-ts= [br] +## See: https://dev.twitch.tv/docs/irc/tags/#clearchat-tags +class ClearChat extends BaseTags: + ## Optional. The message includes this tag if the user was put in a timeout. The tag contains the duration of the timeout, in seconds. + var ban_duration: String + ## The ID of the channel where the messages were removed from. + var room_id: String + ## Optional. The ID of the user that was banned or put in a timeout. The user was banned if the message doesn’t include the ban-duration tag. + var target_user_id: String + ## The UNIX timestamp. + var tmi_sent_ts: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when the bot removes a single message from the chat room. [br] +## @login=;room-id=;target-msg-id=;tmi-sent-ts= [br] +## See: https://dev.twitch.tv/docs/irc/tags/#clearmsg-tags +class ClearMsg extends BaseTags: + ## The name of the user who sent the message. + var login: String + ## Optional. The ID of the channel (chat room) where the message was removed from. + var room_id: String + ## A UUID that identifies the message that was removed. + var target_msg_id: String + ## The UNIX timestamp. + var tmi_sent_ts: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when the bot authenticates with the server. [br] +## @badge-info=;badges=;color=;display-name=;emote-sets=;turbo=;user-id=;user-type= [br] +## See https://dev.twitch.tv/docs/irc/tags/#globaluserstate-tags +class GlobalUserState extends BaseTags: + ## Contains metadata related to the chat badges in the badges tag. [br] + ## Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber. + var badge_info: String + ## Comma-separated list of chat badges in the form, /. For example, admin/1. There are many possible badge values. + var badges: String + ## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #. This tag may be empty if it is never set. + var color: String + ## The user’s display name, escaped as described in the IRCv3 spec. This tag may be empty if it is never set. + var display_name: String + ## A comma-delimited list of IDs that identify the emote sets that the user has access to. Is always set to at least zero (0). To access the emotes in the set, use the Get Emote Sets API. + var emote_sets: String + ## A Boolean value that indicates whether the user has site-wide commercial free mode enabled. Is true (1) if enabled; otherwise, false (0). + var turbo: String + ## The user’s ID. + var user_id: String + ## The type of user. See TwitchTags.USER_TYPE_* + var user_type: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent to indicate the outcome of an action like banning a user. [br] +## @msg-id=;target-user-id= [br] +## See: https://dev.twitch.tv/docs/irc/tags/#notice-tags +class Notice extends BaseTags: + ## An ID that you can use to programmatically determine the action’s outcome. For a list of possible IDs, see NOTICE Message IDs. + var msg_id: String + ## The ID of the user that the action targeted. + var target_user_id: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when a user posts a message to the chat room. [br] +## @badge-info=;badges=;bits=client-nonce=;color=;display-name=;emotes=;first-msg=;flags=;id=;mod=;room-id=;subscriber=;tmi-sent-ts=;turbo=;user-id=;user-type=;reply-parent-msg-id=;reply-parent-user-id=;reply-parent-user-login=;reply-parent-display-name=;reply-parent-msg-body=;reply-thread-parent-msg-id=;reply-thread-parent-user-login=;vip= [br] +## See: https://dev.twitch.tv/docs/irc/tags/#privmsg-tags +class PrivMsg extends BaseTags: + ## Contains metadata related to the chat badges in the badges tag. [br] + ##Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber. + var badge_info: String + ## Comma-separated list of chat badges in the form, /. For example, admin/1. There are many possible badge values, + var badges: String + ## The amount of Bits the user cheered. Only a Bits cheer message includes this tag. To learn more about Bits, see the Extensions Monetization Guide. To get the cheermote, use the Get Cheermotes API. Match the cheer amount to the id field’s value in the response. Then, get the cheermote’s URL based on the cheermote theme, type, and size you want to use. + var bits: String + ## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #. This tag may be empty if it is never set. + var color: String + ## The user’s display name, escaped as described in the IRCv3 spec. This tag may be empty if it is never set. + var display_name: String + ## A comma-delimited list of emotes and their positions in the message. Each emote is in the form, :-. The position indices are zero-based. + var emotes: String + ## An ID that uniquely identifies the message. + var id: String + ## A Boolean value that determines whether the user is a moderator. Is true (1) if the user is a moderator otherwise, false (0). + var mod: String + ## The value of the Hype Chat sent by the user. + var pinned_chat_paid_amount: String + ## The ISO 4217 alphabetic currency code the user has sent the Hype Chat in. + var pinned_chat_paid_currency: String + ## Indicates how many decimal points this currency represents partial amounts in. Decimal points start from the right side of the value defined in pinned-chat-paid-amount. + var pinned_chat_paid_exponent: String + ## The level of the Hype Chat, in English. Possible values are: ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN + var pinned_chat_paid_level: String + ##A Boolean value that determines if the message sent with the Hype Chat was filled in by the system. [br] + ##If true (1), the user entered no message and the body message was automatically filled in by the system. [br] + ##If false (0), the user provided their own message to send with the Hype Chat. + var pinned_chat_paid_is_system_message: String + ## An ID that uniquely identifies the direct parent message that this message is replying to. The message does not include this tag if this message is not a reply. + var reply_parent_msg_id: String + ## An ID that identifies the sender of the direct parent message. The message does not include this tag if this message is not a reply. + var reply_parent_user_id: String + ## The login name of the sender of the direct parent message. The message does not include this tag if this message is not a reply. + var reply_parent_user_login: String + ## The display name of the sender of the direct parent message. The message does not include this tag if this message is not a reply. + var reply_parent_display_name: String + ## The text of the direct parent message. The message does not include this tag if this message is not a reply. + var reply_parent_msg_body: String + ## An ID that uniquely identifies the top-level parent message of the reply thread that this message is replying to. The message does not include this tag if this message is not a reply. + var reply_thread_parent_msg_id: String + ## The login name of the sender of the top-level parent message. The message does not include this tag if this message is not a reply. + var reply_thread_parent_user_login: String + ## An ID that identifies the chat room (channel). + var room_id: String + ## A Boolean value that determines whether the user is a subscriber. Is true (1) if the user is a subscriber otherwise, false (0). + var subscriber: String + ## The UNIX timestamp. + var tmi_sent_ts: String + ## A Boolean value that indicates whether the user has site-wide commercial free mode enabled. Is true (1) if enabled otherwise, false (0). + var turbo: String + ## The user’s ID. + var user_id: String + ## The type of user. See TwitchTags.USER_TYPE_* + var user_type: String + ## A Boolean value that determines whether the user that sent the chat is a VIP. The message includes this tag if the user is a VIP otherwise, the message doesn’t include this tag (check for the presence of the tag instead of whether the tag is set to true or false). + var vip: String + ## Not documented by Twitch. + var first_msg: String + ## Not documented by Twitch. + var client_nonce: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when the bot joins a channel or when the channel’s chat room settings change. [br] +## @emote-only=;followers-only=;r9k=;rituals=;room-id=;slow=;subs-only= [br] +## See: https://dev.twitch.tv/docs/irc/tags/#roomstate-tags +class Roomstate extends BaseTags: + ## A Boolean value that determines whether the chat room allows only messages with emotes. Is true (1) if only emotes are allowed otherwise, false (0). + var emote_only: String + ## An integer value that determines whether only followers can post messages in the chat room. The value indicates how long, in minutes, the user must have followed the broadcaster before posting chat messages. If the value is -1, the chat room is not restricted to followers only. + var followers_only: String + ## A Boolean value that determines whether a user’s messages must be unique. Applies only to messages with more than 9 characters. Is true (1) if users must post unique messages otherwise, false (0). + var r9k: String + ## An ID that identifies the chat room (channel). + var room_id: String + ## An integer value that determines how long, in seconds, users must wait between sending messages. + var slow: String + ## A Boolean value that determines whether only subscribers and moderators can chat in the chat room. Is true (1) if only subscribers and moderators can chat otherwise, false (0). + var subs_only: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when events like someone subscribing to the channel occurs.[br] +## @badge-info=;badges=;color=;display-name=;emotes=;id=;login=;mod=;msg-id=;room-id=;subscriber=;system-msg=;tmi-sent-ts=;turbo=;user-id=;user-type=[br] +## See: https://dev.twitch.tv/docs/irc/tags/#usernotice-tags +class Usernotice extends BaseTags: + ## Contains metadata related to the chat badges in the badges tag. [br] + ## Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber. + var badge_info: String + ## Comma-separated list of chat badges in the form, /. For example, admin/1. There are many possible badge values. + var badges: String + ## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #. This tag may be empty if it is never set. + var color: String + ## The user’s display name, escaped as described in the IRCv3 spec. This tag may be empty if it is never set. + var display_name: String + ## A comma-delimited list of emotes and their positions in the message. Each emote is in the form, :-. The position indices are zero-based. + var emotes: String + ## An ID that uniquely identifies this message. + var id: String + ## The login name of the user whose action generated the message. + var login: String + ## A Boolean value that determines whether the user is a moderator. Is true (1) if the user is a moderator otherwise, false (0). + var mod: String + ## The type of notice (not the ID). Possible values are: TwitchTags.MSG_ID_* + var msg_id: String + ## An ID that identifies the chat room (channel). + var room_id: String + ## A Boolean value that determines whether the user is a subscriber. Is true (1) if the user is a subscriber otherwise, false (0). + var subscriber: String + ## The message Twitch shows in the chat room for this notice. + var system_msg: String + ## The UNIX timestamp for when the Twitch IRC server received the message. + var tmi_sent_ts: String + ## A Boolean value that indicates whether the user has site-wide commercial free mode enabled. Is true (1) if enabled otherwise, false (0). + var turbo: String + ## The user’s ID. + var user_id: String + ## The type of user. See TwitchTags.USER_TYPE_* + var user_type: String +# +# Depending on State +# + ## Included only with sub and resub notices. [br] + ## The total number of months the user has subscribed. This is the same as msg-param-months but sent for different types of user notices. + var msg_param_cumulative_months: String + ## Included only with raid notices. [br] + ## The display name of the broadcaster raiding this channel. + var msg_param_displayName: String + ## Included only with raid notices. [br] + ## The login name of the broadcaster raiding this channel. + var msg_param_login: String + ## Included only with subgift notices. [br] + ## The total number of months the user has subscribed. This is the same as msg-param-cumulative-months but sent for different types of user notices. + var msg_param_months: String + ## Included only with anongiftpaidupgrade and giftpaidupgrade notices. [br] + ## The number of gifts the gifter has given during the promo indicated by msg-param-promo-name. + var msg_param_promo_gift_total: String + ## Included only with anongiftpaidupgrade and giftpaidupgrade notices. [br] + ## The subscriptions promo, if any, that is ongoing (for example, Subtember 2018). + var msg_param_promo_name: String + ## Included only with subgift notices.[br] + ## The display name of the subscription gift recipient. + var msg_param_recipient_display_name: String + ## Included only with subgift notices.[br] + ## The user ID of the subscription gift recipient. + var msg_param_recipient_id: String + ## Included only with subgift notices.[br] + ## The user name of the subscription gift recipient. + var msg_param_recipient_user_name: String + ## Included only with giftpaidupgrade notices. [br] + ## The login name of the user who gifted the subscription. + var msg_param_sender_login: String + ## Include only with giftpaidupgrade notices.[br] + ## The display name of the user who gifted the subscription. + var msg_param_sender_name: String + ## Included only with sub and resub notices.[br] + ## A Boolean value that indicates whether the user wants their streaks shared. + var msg_param_should_share_streak: String + ## Included only with sub and resub notices. + ## The number of consecutive months the user has subscribed. This is zero (0) if msg-param-should-share-streak is 0. + var msg_param_streak_months: String + ## Included only with sub, resub and subgift notices.[br] + ## [br] + ## The type of subscription plan being used. Possible values are:[br] + ## [br] + ## Prime — Amazon Prime subscription[br] + ## 1000 — First level of paid subscription[br] + ## 2000 — Second level of paid subscription[br] + ## 3000 — Third level of paid subscription[br] + var msg_param_sub_plan: String + ## Included only with sub, resub, and subgift notices.[br] + ## The display name of the subscription plan. This may be a default name or one created by the channel owner. + var msg_param_sub_plan_name: String + ## Included only with raid notices.[br] + ## The number of viewers raiding this channel from the broadcaster’s channel. + var msg_param_viewerCount: String + ## Included only with ritual notices.[br] + ## The name of the ritual being celebrated. Possible values are: new_chatter. + var msg_param_ritual_name: String + ## Included only with bitsbadgetier notices.[br] + ## The tier of the Bits badge the user just earned. For example, 100, 1000, or 10000. + var msg_param_threshold: String + ## Included only with subgift notices.[br] + ## The number of months gifted as part of a single, multi-month gift. + var msg_param_gift_months: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when the bot joins a channel or sends a PRIVMSG message. [br] +## @badge-info=;badges=;color=;display-name=;emote-sets=;id=;mod=;subscriber=;turbo=;user-type=[br][br] +## See: https://dev.twitch.tv/docs/irc/tags/#userstate-tags +class Userstate extends BaseTags: + ## Contains metadata related to the chat badges in the badges tag. [br] + ## Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber. + var badge_info: String + ## Comma-separated list of chat badges in the form, /. For example, admin/1. There are many possible badge values. + var badges: String + ## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #. This tag may be empty if it is never set. + var color: String + ## The user’s display name, escaped as described in the IRCv3 spec. This tag may be empty if it is never set. + var display_name: String + ## A comma-delimited list of IDs that identify the emote sets that the user has access to. Is always set to at least zero (0). To access the emotes in the set, use the Get Emote Sets API. + var emote_sets: String + ## If a privmsg was sent, an ID that uniquely identifies the message. + var id: String + ## A Boolean value that determines whether the user is a moderator. Is true (1) if the user is a moderator; otherwise, false (0). + var mod: String + ## A Boolean value that determines whether the user is a subscriber. Is true (1) if the user is a subscriber; otherwise, false (0). + var subscriber: String + ## A Boolean value that indicates whether the user has site-wide commercial free mode enabled. Is true (1) if enabled; otherwise, false (0). + var turbo: String + ## The type of user. See TwitchTags.USER_TYPE_* + var user_type: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + + +## Sent when someone sends your bot a whisper message. [br] +## @badges=;color=;display-name=;emotes=;message-id=;thread-id=;turbo=;user-id=;user-type=[br] +## See: https://dev.twitch.tv/docs/irc/tags/#whisper-tags +class Whisper extends BaseTags: + ## Comma-separated list of chat badges in the form, /. For example, admin/1. There are many possible badge values. + var badges: String + ## The color of the user’s name in the chat room. This is a hexadecimal RGB color code in the form, #. This tag may be empty if it is never set. + var color: String + ## The display name of the user sending the whisper message, escaped as described in the IRCv3 spec. This tag may be empty if it is never set. + var display_name: String + ## A comma-delimited list of emotes and their positions in the message. Each emote is in the form, :-. The position indices are zero-based. + var emotes: String + ## An ID that uniquely identifies the whisper message. + var message_id: String + ## An ID that uniquely identifies the whisper thread. The ID is in the form, _. + var thread_id: String + ## A Boolean value that indicates whether the user has site-wide commercial free mode enabled. Is true (1) if enabled; otherwise, false (0). + var turbo: String + ## The ID of the user sending the whisper message. + var user_id: String + ## The type of user. See TwitchTags.USER_TYPE_* + var user_type: String + + + func _init(tags: String) -> void: + parse_tags(tags, self) + +#endregion diff --git a/addons/twitcher/irc/twitch_tags.gd.uid b/addons/twitcher/irc/twitch_tags.gd.uid new file mode 100644 index 00000000..75c22792 --- /dev/null +++ b/addons/twitcher/irc/twitch_tags.gd.uid @@ -0,0 +1 @@ +uid://drb7ly83s17kp diff --git a/addons/twitcher/lib/README.md b/addons/twitcher/lib/README.md new file mode 100644 index 00000000..a7ea5d08 --- /dev/null +++ b/addons/twitcher/lib/README.md @@ -0,0 +1,6 @@ +Because Godot doesn't have a good system to handle dependencies and +I want to have standalone modules that could be technically their own plugins. + +Requirements: +twitcher requires http, oOuch +oOuch requires http diff --git a/addons/twitcher/lib/http/buffered-http-icon.svg b/addons/twitcher/lib/http/buffered-http-icon.svg new file mode 100644 index 00000000..7bf20ccc --- /dev/null +++ b/addons/twitcher/lib/http/buffered-http-icon.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + diff --git a/addons/twitcher/lib/http/buffered-http-icon.svg.import b/addons/twitcher/lib/http/buffered-http-icon.svg.import new file mode 100644 index 00000000..a5f779b0 --- /dev/null +++ b/addons/twitcher/lib/http/buffered-http-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://517onfw47ulp" +path="res://.godot/imported/buffered-http-icon.svg-338b6a52134b1dc8e217747ec727a304.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/lib/http/buffered-http-icon.svg" +dest_files=["res://.godot/imported/buffered-http-icon.svg-338b6a52134b1dc8e217747ec727a304.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/lib/http/buffered_http_client.gd b/addons/twitcher/lib/http/buffered_http_client.gd new file mode 100644 index 00000000..5310b3f5 --- /dev/null +++ b/addons/twitcher/lib/http/buffered_http_client.gd @@ -0,0 +1,206 @@ +@icon("./buffered-http-icon.svg") +@tool +extends Twitcher + +## Http client that bufferes the requests and sends them sequentialy +class_name BufferedHTTPClient + + +## Will be send when a new request was added to queue +signal request_added(request: RequestData) + +## Will be send when a request is done. +signal request_done(response: ResponseData) + + +## Contains the request data to be send +class RequestData extends RefCounted: + ## The client that the request belongs too + var client: BufferedHTTPClient + ## The request node that is executing the request + var http_request: HTTPRequest + ## Path of the request + var path: String + ## The method that is used to call request + var method: int + ## The request headers + var headers: Dictionary + ## The body that is requested (TODO does it make more sense to make a Byte Array out of it?) + var body: String = "" + ## Amount of retries + var retry: int + + ## When you are done free the request + func queue_free() -> void: + http_request.queue_free() + + +## Contains the response data +class ResponseData extends RefCounted: + ## Result of the request see `HTTPRequest.Result` + var result: int + ## Response code from the request like 200 for OK + var response_code: int + ## the initial request data + var request_data: RequestData + ## The body of the response as byte array + var response_data: PackedByteArray + ## The response header as dictionary, where multiple keys are concatenated with ';' + var response_header: Dictionary + ## Had the response an error + var error: bool + + ## When you are done free the request + func queue_free() -> void: + request_data.queue_free() + +## When a request fails max_error_count then cancel that request -1 for endless amount of tries. +@export var max_error_count : int = -1 +@export var custom_header : Dictionary[String, String] = { "Accept": "*/*" } + +var requests : Array[RequestData] = [] +var current_request : RequestData +var current_response_data : PackedByteArray = PackedByteArray() +var responses : Dictionary = {} +var error_count : int + + +## Only one poll at a time so block for all other tries to call it +var polling: bool +var processing: bool: + get: return not requests.is_empty() || current_request != null + + +## Starts a request that will be handled as soon as the client gets free. +## Use HTTPClient.METHOD_* for the method. +func request(path: String, method: int, headers: Dictionary, body: String) -> RequestData: + logInfo("[%s] start request " % [ path ]) + headers = headers.duplicate() + headers.merge(custom_header) + var req = RequestData.new() + req.path = path + req.method = method + req.body = body + req.headers = headers + req.client = self + req.http_request = HTTPRequest.new() + req.http_request.use_threads = true + req.http_request.timeout = 30 + req.http_request.request_completed.connect(_on_request_completed.bind(req)) + add_child(req.http_request) + var err : Error = req.http_request.request(req.path, _pack_headers(req.headers), req.method, req.body) + if err != OK: logError("Problems with request to %s cause of %s" % [path, error_string(err)]) + requests.append(req) + request_added.emit(req) + logDebug("[%s] request started " % [ path ]) + return req + + +## When the response is available return it otherwise wait for the response +func wait_for_request(request_data: RequestData) -> ResponseData: + if responses.has(request_data): + var response = responses[request_data] + requests.erase(request_data) + responses.erase(request_data) + request_data.queue_free() + logDebug("response cached return directly from wait") + return response + + var latest_response : ResponseData = null + while (latest_response == null || request_data != latest_response.request_data): + latest_response = await request_done + logDebug("response received return from wait") + requests.erase(request_data) + responses.erase(request_data) + request_data.queue_free() + return latest_response + + +func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, request_data: RequestData) -> void: + var response_data : ResponseData = ResponseData.new() + if result != HTTPRequest.Result.RESULT_SUCCESS: + logInfo("[%s] problems with result \n\t> response code: %s \n\t> body: %s" % [request_data.path, response_code, body.get_string_from_utf8()]) + response_data.error = true + if result == HTTPRequest.Result.RESULT_CONNECTION_ERROR || result == HTTPRequest.Result.RESULT_TLS_HANDSHAKE_ERROR: + if request_data.retry == max_error_count: + printerr("Maximum amount of retries for the request. Abort request: %s" % [request_data.path]) + return + var wait_time = pow(2, request_data.retry) + wait_time = min(wait_time, 30) + logDebug("Error happend during connection. Wait for %s" % wait_time) + await get_tree().create_timer(wait_time, true, false, true).timeout + var http_request: HTTPRequest = request_data.http_request.duplicate() + add_child(http_request) + request_data.http_request = http_request + request_data.retry += 1 + http_request.request(request_data.path, _pack_headers(request_data.headers), request_data.method, request_data.body) + http_request.request_completed.connect(_on_request_completed.bind(http_request)) + + response_data.result = result + response_data.request_data = request_data + response_data.response_data = body + response_data.response_code = response_code + response_data.response_header = _get_response_headers_as_dictionary(headers) + responses[request_data] = response_data + logInfo("[%s] request done with result HTTPRequest.Result[%s] " % [ request_data.path, result]) + request_done.emit(response_data) + + +func _get_response_headers_as_dictionary(headers: PackedStringArray) -> Dictionary: + var header_dict: Dictionary = {} + if headers == null: + return header_dict + + for header in headers: + var header_data = header.split(":", true, 1) + var key = header_data[0] + var val = header_data[1] + if header_dict.has(key): + header_dict[key] += "; " + val + else: + header_dict[key] = val + return header_dict + + +func _pack_headers(headers: Dictionary) -> PackedStringArray: + var result: PackedStringArray = [] + for header_key in headers: + var header_value = headers[header_key] + result.append("%s: %s" % [header_key, header_value]) + return result + + +## The amount of requests that are pending +func queued_request_size() -> int: + var requests_size: int = requests.size() + if current_request != null: + requests_size += 1 + return requests_size + + +func empty_response(request_data: RequestData) -> ResponseData: + var response_data = ResponseData.new() + response_data.request_data = request_data + response_data.response_data = [] + response_data.response_code = 0 + response_data.response_header = {} + response_data.result = 0 + return response_data + + +# === LOGGER === + +static var logger: Dictionary = {} +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + logger.debug = debug + logger.info = info + logger.error = error + +static func logDebug(text: String) -> void: + if logger.has("debug"): logger.debug.call(text) + +static func logInfo(text: String) -> void: + if logger.has("info"): logger.info.call(text) + +static func logError(text: String) -> void: + if logger.has("error"): logger.error.call(text) diff --git a/addons/twitcher/lib/http/buffered_http_client.gd.uid b/addons/twitcher/lib/http/buffered_http_client.gd.uid new file mode 100644 index 00000000..097c20bc --- /dev/null +++ b/addons/twitcher/lib/http/buffered_http_client.gd.uid @@ -0,0 +1 @@ +uid://b7i5j62lmuh71 diff --git a/addons/twitcher/lib/http/debug_buffered_http_client.gd b/addons/twitcher/lib/http/debug_buffered_http_client.gd new file mode 100644 index 00000000..1b2a9b0f --- /dev/null +++ b/addons/twitcher/lib/http/debug_buffered_http_client.gd @@ -0,0 +1,58 @@ +extends Control + +@onready var clients: Tree = %Clients + +## Key: BufferedHTTPClient | value: TreeItem +var client_map : Dictionary[BufferedHTTPClient, TreeItem] = {} +## Key: RequestData | value: TreeItem +var request_map : Dictionary[BufferedHTTPClient.RequestData, TreeItem] = {} + + +func _ready() -> void: + get_tree().root.child_entered_tree.connect(_on_child_enter) + _add_http_clients(get_tree().root) + + +func _add_http_clients(parent: Node) -> void: + for child in parent.get_children(): + _on_child_enter(child) + _add_http_clients(child) + + +func _on_child_enter(node: Node) -> void: + if node is BufferedHTTPClient: + _new_client(node) + + +func _new_client(client: BufferedHTTPClient): + var parent = clients.create_item() + parent.set_text(0, client.name) + + client_map[client] = parent + + client.request_added.connect(_on_add_request.bind(parent)) + client.request_done.connect(_on_done_request) + for request in client.requests: + _on_add_request(request, parent) + + +func _on_add_request(request: BufferedHTTPClient.RequestData, http_item: TreeItem): + var request_item = clients.create_item(http_item) + request_item.set_text(0, request.path) + request_item.set_text(1, "Queued") + request_map[request] = request_item + + +func _on_done_request(response: BufferedHTTPClient.ResponseData): + var request_item = request_map[response.request_data] as TreeItem + request_item.set_text(1, "DONE") + await get_tree().create_timer(60, true, false, true).timeout + if request_item != null: request_item.free() + + +func _close_client(client: BufferedHTTPClient): + var http_item = client_map[client] as TreeItem + client_map.erase(client) + http_item.set_text(1, "CLOSED") + await get_tree().create_timer(60, true, false, true).timeout + http_item.free() diff --git a/addons/twitcher/lib/http/debug_buffered_http_client.gd.uid b/addons/twitcher/lib/http/debug_buffered_http_client.gd.uid new file mode 100644 index 00000000..e6ee117d --- /dev/null +++ b/addons/twitcher/lib/http/debug_buffered_http_client.gd.uid @@ -0,0 +1 @@ +uid://bpvg80y7vsysx diff --git a/addons/twitcher/lib/http/debug_buffered_http_client.tscn b/addons/twitcher/lib/http/debug_buffered_http_client.tscn new file mode 100644 index 00000000..16461adb --- /dev/null +++ b/addons/twitcher/lib/http/debug_buffered_http_client.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=2 format=3 uid="uid://b0ebhuv2yaow0"] + +[ext_resource type="Script" uid="uid://bpvg80y7vsysx" path="res://addons/twitcher/lib/http/debug_buffered_http_client.gd" id="1_ij68s"] + +[node name="DebugBufferedHttpClient" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_ij68s") + +[node name="Label" type="Label" parent="."] +layout_mode = 2 +text = "Debug HTTP Clients" +horizontal_alignment = 1 + +[node name="Clients" type="Tree" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 +hide_root = true diff --git a/addons/twitcher/lib/http/http_server.gd b/addons/twitcher/lib/http/http_server.gd new file mode 100644 index 00000000..a5f56abb --- /dev/null +++ b/addons/twitcher/lib/http/http_server.gd @@ -0,0 +1,182 @@ +@tool +extends Node + +## Provides a simple HTTP Service to serve web stuff +class_name HTTPServer + +## Key: int | Value: WeakRef(Server) +static var _servers : Dictionary = {} + + +class Server extends TCPServer: + var _bind_address: String + var _port: int + var _clients: Array[Client] = [] + var _listeners: int + + signal request_received(client: Client) + signal client_connected(client: Client) + signal client_disconnected(client: Client) + signal client_error_occured(client: Client, error: Error) + + + func _init(bind_address: String, port: int) -> void: + _bind_address = bind_address + _port = port + HTTPServer._servers[_port] = weakref(self) + + + func start_listening() -> void: + _listeners += 1 + if !is_listening(): + var status: Error = listen(_port, _bind_address) + Engine.get_main_loop().process_frame.connect(_process) + if status != OK: + HTTPServer.logError("Could not listen to port %d: %s" % [_port, error_string(status)]) + else: + HTTPServer.logInfo("{%s:%s} listening" % [ _bind_address, _port ]) + else: + HTTPServer.logDebug("{%s:%s} already listening" % [ _bind_address, _port ]) + + + func stop_listening() -> void: + _listeners -= 1 + HTTPServer.logDebug("{%s:%s} listener node detached %s left" % [ _bind_address, _port, _listeners ]) + if _listeners <= 0: + _stop_server() + + + func _stop_server() -> void: + HTTPServer.logInfo("{%s:%s} stop" % [ _bind_address, _port ]) + Engine.get_main_loop().process_frame.disconnect(_process) + for client in _clients: + client.peer.disconnect_from_host() + stop() + + + func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + HTTPServer.logInfo("{%s:%s} removed" % [ _bind_address, _port ]) + HTTPServer._servers.erase(_port) + + + func _process() -> void: + if !is_listening(): return + + if is_connection_available(): + _handle_connect() + + for client in _clients: + _process_request(client) + _handle_disconnect(client) + + func _process_request(client: Client) -> void: + var peer := client.peer + + var error := peer.poll() + if error != OK: + HTTPServer.logError("Could not poll client %d: %s" % [_port, error_string(error)]) + client_error_occured.emit(client, error) + return + + if peer.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return + + var avail := peer.get_available_bytes() + if avail > 0: + request_received.emit(client) + + + func _handle_connect() -> void: + var peer := take_connection() + var client := Client.new() + client.peer = peer + _clients.append(client) + client_connected.emit(client) + HTTPServer.logInfo("{%s:%s} client connected" % [ _bind_address, _port ]) + + + func _handle_disconnect(client: Client) -> void: + if client.peer.get_status() != StreamPeerTCP.STATUS_CONNECTED: + client_disconnected.emit(client) + HTTPServer.logInfo("{%s:%s} client disconnected" % [ _bind_address, _port ]) + _clients.erase(client) + + +class Client extends RefCounted: + var peer: StreamPeerTCP + + +## Called when a new request was made +signal request_received(client: Client) + + +@export var _port: int +@export var _bind_address: String + +var _server : Server +var _listening: bool + + +static func create(port: int, bind_address: String = "*") -> HTTPServer: + var server = HTTPServer.new() + server._bind_address = bind_address + server._port = port + return server + + +func _ready() -> void: + if _servers.has(_port) && _servers[_port] != null: + _server = _servers[_port].get_ref() + else: + _server = Server.new(_bind_address, _port) + _server.request_received.connect(_on_request_received) + logInfo("{%s:%s} start" % [ _bind_address, _port ]) + + +func _on_request_received(client: Client) -> void: + request_received.emit(client) + + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + stop_listening() + + +func start_listening() -> void: + _listening = true + _server.start_listening() + + +func stop_listening() -> void: + if _listening: + _listening = false + _server.stop_listening() + + +func send_response(client: Client, response_code : String, body : PackedByteArray) -> void: + var peer = client.peer + peer.put_data(("HTTP/1.1 %s\r\n" % response_code).to_utf8_buffer()) + peer.put_data("Server: Godot Engine (Twitcher)\r\n".to_utf8_buffer()) + peer.put_data(("Content-Length: %d\r\n"% body.size()).to_utf8_buffer()) + peer.put_data("Connection: close\r\n".to_utf8_buffer()) + peer.put_data("Content-Type: text/html charset=UTF-8\r\n".to_utf8_buffer()) + peer.put_data("\r\n".to_utf8_buffer()) + peer.put_data(body) + + +# === LOGGER === +static var logger: Dictionary = {} +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + logger.debug = debug + logger.info = info + logger.error = error + +static func logDebug(text: String) -> void: + if logger.has("debug"): logger.debug.call(text) + +static func logInfo(text: String) -> void: + if logger.has("info"): logger.info.call(text) + +static func logError(text: String) -> void: + if logger.has("error"): logger.error.call(text) diff --git a/addons/twitcher/lib/http/http_server.gd.uid b/addons/twitcher/lib/http/http_server.gd.uid new file mode 100644 index 00000000..e08f3ad5 --- /dev/null +++ b/addons/twitcher/lib/http/http_server.gd.uid @@ -0,0 +1 @@ +uid://bnepo370sikkb diff --git a/addons/twitcher/lib/http/http_util.gd b/addons/twitcher/lib/http/http_util.gd new file mode 100644 index 00000000..2d8342af --- /dev/null +++ b/addons/twitcher/lib/http/http_util.gd @@ -0,0 +1,25 @@ +extends Object + +## Parses a query string and returns a dictionary with the parameters. +static func parse_query(query: String) -> Dictionary: + var parameters = Dictionary() + # Split the query by '&' to separate different parameters. + var pairs = query.split("&") + # Iterate over each pair of key-value. + for pair in pairs: + # Split the pair by '=' to separate the key from the value. + var kv = pair.split("=") + if kv.size() == 2: + var key = kv[0].strip_edges() + var value = kv[1].strip_edges() + var decoded_key = key.uri_decode() + var decoded_value = value.uri_decode() + parameters[decoded_key] = decoded_value + return parameters + + +## Method to set all logger within this package +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + BufferedHTTPClient.set_logger(error, info, debug) + HTTPServer.set_logger(error, info, debug) + WebsocketClient.set_logger(error, info, debug) diff --git a/addons/twitcher/lib/http/http_util.gd.uid b/addons/twitcher/lib/http/http_util.gd.uid new file mode 100644 index 00000000..36487711 --- /dev/null +++ b/addons/twitcher/lib/http/http_util.gd.uid @@ -0,0 +1 @@ +uid://5esrbr8ikth8 diff --git a/addons/twitcher/lib/http/websocket_client.gd b/addons/twitcher/lib/http/websocket_client.gd new file mode 100644 index 00000000..8c0aebc1 --- /dev/null +++ b/addons/twitcher/lib/http/websocket_client.gd @@ -0,0 +1,145 @@ +@tool +extends Node + +## Advanced websocket client that automatically reconnects to the server +class_name WebsocketClient + + +## Called as soon the websocket got a connection +signal connection_established + +## Called as soon the websocket closed the connection +signal connection_closed + +## Called when a complete message got received +signal message_received(message: PackedByteArray) + +## Called when the state of the websocket changed +signal connection_state_changed(state : WebSocketPeer.State) + +@export var connection_url: String: + set(val): + _logDebug("Set connection to %s" % val) + connection_url = val + +var connection_state : WebSocketPeer.State = WebSocketPeer.STATE_CLOSED: + set(new_state): + if new_state != connection_state: + connection_state_changed.emit(new_state) + if new_state == WebSocketPeer.STATE_OPEN: connection_established.emit() + if new_state == WebSocketPeer.STATE_CLOSED: connection_closed.emit() + connection_state = new_state + +## Determines if a connection should be established or not +@export var auto_reconnect: bool: + set(val): + auto_reconnect = val + _logDebug("New auto_reconnect value: %s" % val) + +## True if currently connecting to prevent 2 connectionen processes at the same time +var _is_already_connecting: bool +var is_open: bool: + get(): return _peer.get_ready_state() == WebSocketPeer.STATE_OPEN +var is_closed: bool: + get(): return _peer.get_ready_state() == WebSocketPeer.STATE_CLOSED + +var _peer: WebSocketPeer = WebSocketPeer.new() +var _tries: int + + +func open_connection() -> void: + if not is_closed: return + auto_reconnect = true + _logInfo("Open connection") + await _establish_connection() + + +func wait_connection_established() -> void: + if is_open: return + await connection_established + +func _establish_connection() -> void: + if _is_already_connecting || not is_closed: return + _is_already_connecting = true + var wait_time = pow(2, _tries) + _logDebug("Wait %s before connecting" % [wait_time]) + await get_tree().create_timer(wait_time, true, false, true).timeout + _logInfo("Connecting to %s" % connection_url) + var err = _peer.connect_to_url(connection_url) + if err != OK: + logError("Couldn't connect cause of %s" % [error_string(err)]) + _tries += 1 + _is_already_connecting = false + + +func _enter_tree() -> void: + if auto_reconnect: open_connection() + + +func _exit_tree() -> void: + if not is_open: return + _peer.close(1000, "resource got freed") + + +func _process(delta: float) -> void: + _poll() + + +func _poll() -> void: + if connection_url == "": return + + var state := _peer.get_ready_state() + if state == WebSocketPeer.STATE_CLOSED and auto_reconnect: + _establish_connection() + _peer.poll() + _handle_state_changes(state) + connection_state = state + if state == WebSocketPeer.STATE_OPEN: + _read_data() + + +func _handle_state_changes(state: WebSocketPeer.State) -> void: + if connection_state != WebSocketPeer.STATE_OPEN && state == WebSocketPeer.STATE_OPEN: + _logInfo("connected") + _tries = 0 + + if connection_state != WebSocketPeer.STATE_CLOSED && state == WebSocketPeer.STATE_CLOSED: + _logInfo("connection was closed [%s]: %s" % [_peer.get_close_code(), _peer.get_close_reason()]) + + +func _read_data() -> void: + while (_peer.get_available_packet_count()): + message_received.emit(_peer.get_packet()) + + +func send_text(message: String) -> Error: + return _peer.send_text(message) + + +func close(status: int = 1000, message: String = "Normal Closure") -> void: + _logDebug("Websocket activly closed") + auto_reconnect = false + _peer.close(status, message) + + +# === LOGGER === +static var logger: Dictionary = {} +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + logger.debug = debug + logger.info = info + logger.error = error + +func _logDebug(text: String) -> void: + logDebug("[%s]: %s" % [connection_url, text]) + +static func logDebug(text: String) -> void: + if logger.has("debug"): logger.debug.call(text) + +func _logInfo(text: String) -> void: + logInfo("[%s]: %s" % [connection_url, text]) + +static func logInfo(text: String) -> void: + if logger.has("info"): logger.info.call(text) + +static func logError(text: String) -> void: + if logger.has("error"): logger.error.call(text) diff --git a/addons/twitcher/lib/http/websocket_client.gd.uid b/addons/twitcher/lib/http/websocket_client.gd.uid new file mode 100644 index 00000000..8c2ebb64 --- /dev/null +++ b/addons/twitcher/lib/http/websocket_client.gd.uid @@ -0,0 +1 @@ +uid://buqbforpa7b8a diff --git a/addons/twitcher/lib/oOuch/crypto_key_provider.gd b/addons/twitcher/lib/oOuch/crypto_key_provider.gd new file mode 100644 index 00000000..795c77c9 --- /dev/null +++ b/addons/twitcher/lib/oOuch/crypto_key_provider.gd @@ -0,0 +1,94 @@ +@icon("./security-icon.svg") +@tool +extends Resource + +## Provides a key to encrypt secrets in the application. +## Please don't store the key in the project, +## otherwise your secrets may revealed easily! +class_name CryptoKeyProvider + +## Identify oOuch library specifics without collisions +const _CONFIG_PACKAGE_KEY: String = "dev.kani.oouch" +const _CONFIG_SECRET_KEY: String = "encryption" +const _AES_BLOCK_SIZE : int = 16 + +## Location of the encryption secrets +@export_global_file var encrpytion_secret_location: String = "user://encryption_key.cfg" + +static var aes: AESContext = AESContext.new() + +## To prevent accidental spoiler in the debugger +class KeyData extends RefCounted: + var key: String + +var current_key_data: KeyData + +func _init() -> void: + # Call defered cause the setter of encrpytion_secret_location isn't set otherwise + _get_encryption_secret.call_deferred() + + +## Don't cache it in a variable so that you accidently leak your secret when you debug +func _get_encryption_secret() -> String: + if is_instance_valid(current_key_data): + return current_key_data.key + + var config = ConfigFile.new() + var error = config.load(encrpytion_secret_location) + if error == ERR_FILE_NOT_FOUND: + _create_secret(config) + elif error != OK: + printerr("Can't open %s cause of %s" % [encrpytion_secret_location, error_string(error)]) + return "" + + var key: String = config.get_value(_CONFIG_PACKAGE_KEY, _CONFIG_SECRET_KEY, "") + if key == "": + key = _create_secret(config) + + current_key_data = KeyData.new() + current_key_data.key = key + return key + + +func _create_secret(config: ConfigFile) -> String: + print("Creating a new secret for encryption you can find it %s" % encrpytion_secret_location) + var crypto : Crypto = Crypto.new() + + var secret_data : PackedByteArray = crypto.generate_random_bytes(16) + var secret : String = secret_data.hex_encode() + config.set_value(_CONFIG_PACKAGE_KEY, _CONFIG_SECRET_KEY, secret) + var err = config.save(encrpytion_secret_location) + if err != OK: push_error("Couldn't save encryption key cause of ", error_string(err)) + return secret + + +func _pad(value: PackedByteArray) -> PackedByteArray: + var pad_len : int = _AES_BLOCK_SIZE - (value.size() % _AES_BLOCK_SIZE) + for i in range(pad_len): + value.append(pad_len) + return value + + +func _unpad(value: PackedByteArray) -> PackedByteArray: + if value.is_empty(): + return value + var pad_len : int = value[-1] + if pad_len <= 0 or pad_len > _AES_BLOCK_SIZE or value.size() < pad_len: + push_error("Invalid padding detected (%s)" % pad_len) + return PackedByteArray() + return value.slice(0, -pad_len) + + +func encrypt(value: PackedByteArray) -> PackedByteArray: + var padded_value = _pad(value) + aes.start(AESContext.MODE_ECB_ENCRYPT, _get_encryption_secret().to_utf8_buffer()) + var encrypted_value: PackedByteArray = aes.update(padded_value) + aes.finish() + return encrypted_value + + +func decrypt(value: PackedByteArray) -> PackedByteArray: + aes.start(AESContext.MODE_ECB_DECRYPT, _get_encryption_secret().to_utf8_buffer()) + var decrypted_value: PackedByteArray = aes.update(value) + aes.finish() + return _unpad(decrypted_value) diff --git a/addons/twitcher/lib/oOuch/crypto_key_provider.gd.uid b/addons/twitcher/lib/oOuch/crypto_key_provider.gd.uid new file mode 100644 index 00000000..d49a165f --- /dev/null +++ b/addons/twitcher/lib/oOuch/crypto_key_provider.gd.uid @@ -0,0 +1 @@ +uid://dcrliedgr6eol diff --git a/addons/twitcher/lib/oOuch/default_key_provider.tres b/addons/twitcher/lib/oOuch/default_key_provider.tres new file mode 100644 index 00000000..ea2fb111 --- /dev/null +++ b/addons/twitcher/lib/oOuch/default_key_provider.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="CryptoKeyProvider" load_steps=2 format=3 uid="uid://c4scwuk8q0r40"] + +[ext_resource type="Script" uid="uid://dcrliedgr6eol" path="res://addons/twitcher/lib/oOuch/crypto_key_provider.gd" id="1_q12uq"] + +[resource] +script = ExtResource("1_q12uq") +encrpytion_secret_location = "user://encryption_key.cfg" diff --git a/addons/twitcher/lib/oOuch/oauth.gd b/addons/twitcher/lib/oOuch/oauth.gd new file mode 100644 index 00000000..ff2c432c --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth.gd @@ -0,0 +1,403 @@ +@icon("./security-icon.svg") +@tool +extends Node + +## Orchestrates the complete authentication process +class_name OAuth + +const OAuthHTTPServer = preload("res://addons/twitcher/lib/http/http_server.gd") +const OAuthHTTPClient = preload("res://addons/twitcher/lib/http/buffered_http_client.gd") +const OAuthDeviceCodeResponse = preload("./oauth_device_code_response.gd") + +## A static string in front of the sensible data to prevent accidental leak of tokens during debug sessions +const DEBUGGER_PROTECTION: String = " " + +## Called when the authorization for AuthCodeFlow is complete to handle the auth code +signal _auth_succeed(code: String) + +## In case the authorization wasn't succesfull +signal auth_error(error: String, error_description: String) + +## The requested devicecode to show to the user for authorization +signal device_code_requested(device_code: OAuthDeviceCodeResponse) + +## Called when the token has changed +signal token_changed(access_token: String) + +@export var oauth_setting: OAuthSetting +@export var scopes: OAuthScopes +@export var token_handler: OAuthTokenHandler +## Customize how you want to open the authorization page (advanced usage for example multi user authentication) +@export var shell_command: String +## Parameters for the shell command (advanced usage for example multi user authentication) +@export var shell_parameter: Array[String] = [] +## Some oauth provide doesn't return the provided scopes so you can disable the scope check +@export var check_scope_changed: bool = true +## Should the Twitch special handling be activated (Twitch is behaving differntly as the normal Oauth provider and needs SpEcIal treatment) +@export var enable_twitch_hacks: bool = true + +var login_in_process: bool +## Special solution just for twitch ignore it in all other providers +var force_verify: String +var _query_parser = RegEx.create_from_string("GET (.*?/?)\\??(.*?)? HTTP/1\\.1.*?") +var _auth_http_server: OAuthHTTPServer +var _last_login_attempt: int +## State for the current authcode request to compare with +var _current_state: String +var _client: OAuthHTTPClient +var _crypto: Crypto = Crypto.new() +var _login_timeout_timer: Timer +var _initialized: bool + +enum AuthorizationFlow { + AUTHORIZATION_CODE_FLOW, + IMPLICIT_FLOW, + DEVICE_CODE_FLOW, + CLIENT_CREDENTIALS +} + + +func _on_unauthenticated() -> void: + login() + + +func _on_token_resolved(token: OAuthToken) -> void: + if token == null: return + token_changed.emit(await token.get_access_token()) + + +## Checks if the authentication is valid. +func is_authenticated() -> bool: + return token_handler.is_token_valid() + + +## Starts the token refresh process to rotate the tokens +func refresh_token() -> void: + await token_handler.refresh_tokens() + + +func _setup_nodes() -> void: + if _initialized: return + _initialized = true + + if _client == null: + _client = OAuthHTTPClient.new() + _client.name = "OAuthClient" + add_child(_client) + + if _auth_http_server == null: + _auth_http_server = OAuthHTTPServer.create(oauth_setting.redirect_port) + _auth_http_server.name = "OAuthServer" + add_child(_auth_http_server) + + if token_handler == null: + token_handler = OAuthTokenHandler.new() + add_child(token_handler) + + token_handler.unauthenticated.connect(_on_unauthenticated) + token_handler.token_resolved.connect(_on_token_resolved) + + _login_timeout_timer = Timer.new() + _login_timeout_timer.name = "LoginTimeoutTimer" + _login_timeout_timer.one_shot = true + _login_timeout_timer.wait_time = 30 + _login_timeout_timer.timeout.connect(_on_login_timeout) + add_child(_login_timeout_timer) + +func do_unsetup() -> void: + _last_login_attempt = 0 + +## Depending on the authorization_flow it gets resolves the token via the different +## Flow types. Only one login process at the time. All other tries wait until the first process +## was succesful. +func login() -> bool: + if not is_node_ready(): await ready + _setup_nodes() + if token_handler.is_token_valid() && not _got_scopes_changed(): return true + logDebug("Token is valid (%s) and not scopes changed (%s)" % [ token_handler.is_token_valid(), _got_scopes_changed()]) + + if login_in_process: + logInfo("Another process tries already to login. Abort") + if (await token_handler.token_resolved) == null: + return false + return true + + if _last_login_attempt != 0 && Time.get_ticks_msec() - 60 * 1000 < _last_login_attempt: + print("[OAuth] Last Login attempt was within 1 minute wait 1 minute before trying again. Please enable and consult logs, cause there is an issue with your authentication!") + await get_tree().create_timer(60, true, false, true).timeout + + _last_login_attempt = Time.get_ticks_msec() + + login_in_process = true + _login_timeout_timer.start() + logInfo("do login") + match oauth_setting.authorization_flow: + AuthorizationFlow.AUTHORIZATION_CODE_FLOW: + await _start_login_process("code") + AuthorizationFlow.CLIENT_CREDENTIALS: + await _start_client_credential_process() + AuthorizationFlow.IMPLICIT_FLOW: + await _start_login_process("token") + AuthorizationFlow.DEVICE_CODE_FLOW: + await _start_device_login_process() + + login_in_process = false + _login_timeout_timer.stop() + return true + + +func _got_scopes_changed() -> bool: + if not check_scope_changed: return false + + var existing_scopes = token_handler.get_scopes() + var requested_scopes = scopes.used_scopes + if existing_scopes.size() != requested_scopes.size(): + return true + + for scope in existing_scopes: + if requested_scopes.find(scope) == -1: + return true + + return false + + +## Called when the login process is timing out cause of misconfiguration or other natural catastrophes. +func _on_login_timeout() -> void: + if token_handler.is_token_valid(): return + logError("Login run into a timeout. Stop all login processes.") + _auth_succeed.emit("") + token_handler.token_resolved.emit(null) + + +func _start_login_process(response_type: String) -> void: + if scopes == null: scopes = OAuthScopes.new() + + _auth_http_server.start_listening() + + if response_type == "code": + _auth_http_server.request_received.connect(_process_code_request.bind(_auth_http_server)) + elif response_type == "token": + _auth_http_server.request_received.connect(_process_implicit_request.bind(_auth_http_server)) + + _current_state = _crypto.generate_random_bytes(16).hex_encode() + var query_param = "&".join([ + "force_verify=%s" % force_verify.uri_encode(), + "response_type=%s" % response_type.uri_encode(), + "client_id=%s" % oauth_setting.client_id.uri_encode(), + "scope=%s" % scopes.ssv_scopes().uri_encode(), + "redirect_uri=%s" % oauth_setting.redirect_url.uri_encode(), + "state=%s" % _current_state + ]) + + var url = oauth_setting.authorization_url + "?" + query_param + logInfo("start login process to get token for scopes %s" % (",".join(scopes.used_scopes))) + logDebug("login to %s" % url) + if not shell_command.is_empty(): + var parameters: PackedStringArray = shell_parameter.duplicate() \ + .map(func(param: String): return param.format({"url": url})) + OS.create_process(shell_command, parameters) + else: + OS.shell_open(url) + + logDebug("waiting for user to login.") + if response_type == "code": + var auth_code = await _auth_succeed + if auth_code == "": + logDebug("Auth code was empty. Abort Login.") + return + token_handler.request_token("authorization_code", auth_code) + await token_handler.token_resolved + _auth_http_server.request_received.disconnect(_process_code_request.bind(_auth_http_server)) + elif response_type == "token": + await token_handler.token_resolved + _auth_http_server.request_received.disconnect(_process_implicit_request.bind(_auth_http_server)) + logInfo("authorization is done stop server") + _auth_http_server.stop_listening() + +#region DeviceCodeFlow + + +## Starts the device flow. +func _start_device_login_process(): + var scopes: String = " ".join(scopes.used_scopes) + var device_code_response: OAuthDeviceCodeResponse = await _fetch_device_code_response(scopes) + device_code_requested.emit(device_code_response) + + # print the information instead of opening the browser so that the developer can decide if + # he want to open the browser manually. Also use print not the logger so that the information + # is sent always. + print("Visit %s and enter the code %s for authorization." % [device_code_response.verification_uri, device_code_response.user_code]) + await token_handler.request_device_token(device_code_response, scopes) + + +func _fetch_device_code_response(scopes: String) -> OAuthDeviceCodeResponse: + logInfo("Start device code flow") + logDebug("Request Scopes: %s" % scopes) + var body = "client_id=%s&scopes=%s" % [oauth_setting.client_id, scopes.uri_encode()] + var request = _client.request(oauth_setting.device_authorization_url, HTTPClient.METHOD_POST, { + "Content-Type": "application/x-www-form-urlencoded" + }, body) + + var initial_response_data = await _client.wait_for_request(request) + if initial_response_data.response_code != 200: + logError("Couldn't initiate device code flow response code %s" % initial_response_data.response_code) + var initial_response_string = initial_response_data.response_data.get_string_from_ascii() + var initial_response_dict = JSON.parse_string(initial_response_string) as Dictionary + return OAuthDeviceCodeResponse.new(initial_response_dict) + +#endregion +#region ImplicitFlow + +## Handles the response after auth endpoint redirects to our server with the response +func _process_implicit_request(client: OAuthHTTPServer.Client, server: OAuthHTTPServer) -> void: + var request = client.peer.get_utf8_string(client.peer.get_available_bytes()) + if request == "": + logError("Empty response. Check if your redirect URL is set to %s." % oauth_setting.redirect_url) + client.peer.disconnect_from_host() + return + + var first_linebreak = request.find("\n") + var first_line = request.substr(0, first_linebreak) + if first_line.begins_with("GET"): + var matcher = _query_parser.search(first_line) + if matcher == null: + logDebug("Response from auth server was not right expected redirect url. It's ok browser asked probably for favicon etc.") + return + var redirect_path = oauth_setting.redirect_path + var request_path = matcher.get_string(1) + if redirect_path == request_path: + server.send_response(client, "200 OK", ("Login + + Redirect Token to Godot + ").to_utf8_buffer()) + logInfo("Send Response to send it via POST") + elif first_line.begins_with("POST"): + var parts = request.split("\r\n\r\n") + if parts.size() < 2: + return # Not a valid request + var json_body = parts[1] + var token_request: Variant = JSON.parse_string(json_body) + if token_request["state"] != _current_state: + server.send_response(client, "200 OK", "LoginUnsuccessful someone tampered with the state! See Twitch Documentation for more information.".to_utf8_buffer()) + return + + var scopes: PackedStringArray = token_request["scope"].split(" ") + if enable_twitch_hacks && token_handler is TwitchTokenHandler: + var token: String = OAuth.DEBUGGER_PROTECTION + token_request["access_token"] + var validation_response: BufferedHTTPClient.ResponseData = await token_handler.validate_token(token) + var validation_data: Variant = JSON.parse_string(validation_response.response_data.get_string_from_utf8()) + token_handler.update_tokens(token, "", validation_data["expires_in"], scopes) + else: + token_handler.update_tokens(token_request["access_token"], "", token_request["expires_in"], scopes) + logInfo("Received Access Token update it") + server.send_response(client, "200 OK", "LoginSuccess!".to_utf8_buffer()) + +#endregion +#region AuthCodeFlow +## Handles the response after auth endpoint redirects to our server with the response +func _process_code_request(client: OAuthHTTPServer.Client, server: OAuthHTTPServer) -> void: + if client.peer.get_status() != StreamPeerTCP.STATUS_CONNECTED: + logError("Client not connected can't process code response.") + return + + var request = client.peer.get_utf8_string(client.peer.get_available_bytes()) + if request == "": + logError("Empty response. Check if your redirect URL is set to %s." % oauth_setting.redirect_url) + client.peer.disconnect_from_host() + return + + # Firstline contains request path and parameters + var first_line = request.substr(0, request.find("\n")) + var matcher = _query_parser.search(first_line) + if matcher == null: + logDebug("Response from auth server was not right expected query params. It's ok browser asked probably for favicon etc.") + client.peer.disconnect_from_host() + return + + var query_params_str = matcher.get_string(2) + var query_params = parse_query(query_params_str) + + var state = query_params.get("state") + if query_params.has("error"): + _handle_error(server, client, query_params) + elif state == _current_state: + _handle_success(server, client, query_params) + else: + _handle_other_requests(server, client, first_line) + client.peer.disconnect_from_host() + + +## Returns the response for the given auth request back to the browser also emits the auth code +func _handle_success(server: OAuthHTTPServer, client: OAuthHTTPServer.Client, query_params : Dictionary) -> void: + logInfo("Authentication success. Send auth code.") + if query_params.has("code"): + var succes_page = FileAccess.get_file_as_bytes("res://addons/twitcher/assets/success-page.txt") + server.send_response(client, "200 OK", succes_page) + _auth_succeed.emit(query_params['code']) + else: + var error_page = FileAccess.get_file_as_bytes("res://addons/twitcher/assets/error-page.txt") + server.send_response(client, "200 OK", error_page) + logError("Auth code expected wasn't send!") + + +## Handles the error in case that Auth API has a problem +func _handle_error(server: OAuthHTTPServer, client: OAuthHTTPServer.Client, query_params : Dictionary) -> void: + var msg = "Error %s: %s" % [query_params["error"], query_params["error_description"]] + logError(msg) + server.send_response(client, "400 BAD REQUEST", msg.to_utf8_buffer()) + +#endregion +#region ClientCredentialFlow +func _start_client_credential_process() -> void: + var token = await token_handler.request_token("client_credentials") + if enable_twitch_hacks: + # There is a reason why this is in twitch hacks =__= + # aka client credentials doesn't give you the scopes anywhere + token._update_scopes(scopes.used_scopes) + +#endregion + +func _handle_other_requests(server: OAuthHTTPServer, client: OAuthHTTPServer.Client, fist_line: String) -> void: + if fist_line.contains("favicon.ico"): + var favicon: PackedByteArray = FileAccess.get_file_as_bytes("res://addons/twitcher/assets/favicon.ico") + server.send_response(client, "200", favicon) + + +## Parses a query string and returns a dictionary with the parameters. +static func parse_query(query: String) -> Dictionary: + var parameters = Dictionary() + # Split the query by '&' to separate different parameters. + var pairs = query.split("&") + # Iterate over each pair of key-value. + for pair in pairs: + # Split the pair by '=' to separate the key from the value. + var kv = pair.split("=") + if kv.size() == 2: + var key = kv[0].strip_edges() + var value = kv[1].strip_edges() + var decoded_key = key.uri_decode() + var decoded_value = value.uri_decode() + parameters[decoded_key] = decoded_value + return parameters + + +# === LOGGER === +static var logger: Dictionary = {} +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + logger.debug = debug + logger.info = info + logger.error = error + OAuthTokenHandler.set_logger(error, info, debug) + +static func logDebug(text: String) -> void: + if logger.has("debug"): logger.debug.call(text) + +static func logInfo(text: String) -> void: + if logger.has("info"): logger.info.call(text) + +static func logError(text: String) -> void: + if logger.has("error"): logger.error.call(text) diff --git a/addons/twitcher/lib/oOuch/oauth.gd.uid b/addons/twitcher/lib/oOuch/oauth.gd.uid new file mode 100644 index 00000000..21cc596d --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth.gd.uid @@ -0,0 +1 @@ +uid://bf0wi70haua35 diff --git a/addons/twitcher/lib/oOuch/oauth_device_code_response.gd b/addons/twitcher/lib/oOuch/oauth_device_code_response.gd new file mode 100644 index 00000000..a469c692 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_device_code_response.gd @@ -0,0 +1,16 @@ +extends RefCounted + +## Response of the inital device code request + +var device_code: String; +var expires_in: int; +var interval: int; +var user_code: String; +var verification_uri: String; + +func _init(json: Dictionary): + device_code = json["device_code"]; + expires_in = int(json["expires_in"]); + interval = int(json["interval"]); + user_code = json["user_code"]; + verification_uri = json["verification_uri"]; diff --git a/addons/twitcher/lib/oOuch/oauth_device_code_response.gd.uid b/addons/twitcher/lib/oOuch/oauth_device_code_response.gd.uid new file mode 100644 index 00000000..cd39058d --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_device_code_response.gd.uid @@ -0,0 +1 @@ +uid://doqj7o6fwi8dp diff --git a/addons/twitcher/lib/oOuch/oauth_scopes.gd b/addons/twitcher/lib/oOuch/oauth_scopes.gd new file mode 100644 index 00000000..21f946e5 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_scopes.gd @@ -0,0 +1,31 @@ +@icon("./scope-icon.svg") +@tool +extends Resource + +## Contains the information about a set of scopes. +class_name OAuthScopes + +## Called when new scopes was added or removed +signal scopes_changed + +@export var used_scopes: Array[StringName] = []: + set(val): + used_scopes = val; + scopes_changed.emit() + + +## Returns the scopes space separated +func ssv_scopes() -> String: + return " ".join(used_scopes) + + +func add_scopes(scopes: Array[StringName]) -> void: + for scope in scopes: + if used_scopes.find(scope) != -1: continue + used_scopes.append(scope) + scopes_changed.emit() + + +func remove_scopes(scopes: Array[StringName]) -> void: + used_scopes = used_scopes.filter(func(s): return scopes.find(s) != -1) + scopes_changed.emit() diff --git a/addons/twitcher/lib/oOuch/oauth_scopes.gd.uid b/addons/twitcher/lib/oOuch/oauth_scopes.gd.uid new file mode 100644 index 00000000..11cc80f2 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_scopes.gd.uid @@ -0,0 +1 @@ +uid://dexweyb521tu0 diff --git a/addons/twitcher/lib/oOuch/oauth_setting.gd b/addons/twitcher/lib/oOuch/oauth_setting.gd new file mode 100644 index 00000000..754f7908 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_setting.gd @@ -0,0 +1,112 @@ +@icon("./scope-icon.svg") +@tool +extends Resource +class_name OAuthSetting + + +## That will be called when the authcode was received to send the code to the backend +@export var redirect_url: String = "http://localhost:7170": + set = _update_redirect_url +## Wellknown endpoint to receive the common paths for the IAM provider (optional) +@export var well_known_url: String +## Path where tokens can be get +@export var token_url: String +## Path to the authorization endpoint +@export var authorization_url: String +## Path to the device code flow URL. +@export var device_authorization_url: String +## Where should the tokens be cached +@export var cache_file: String = "res://auth.key" +## Client ID to authorize +@export var client_id: String: + set(val): + client_id = val + emit_changed() +## Defines the authorization flow. +@export var authorization_flow: OAuth.AuthorizationFlow = OAuth.AuthorizationFlow.AUTHORIZATION_CODE_FLOW: + set(val): + authorization_flow = val + notify_property_list_changed() + emit_changed() + +@export var _encryption_key_provider: CryptoKeyProvider = preload("res://addons/twitcher/lib/oOuch/default_key_provider.tres") + +# Calculated Values +var redirect_path: String: + get(): + if redirect_path == "" and redirect_url != "": _update_redirect_url(redirect_url) + return redirect_path +var redirect_port: int: + get(): + if redirect_port == 0 and redirect_url != "": _update_redirect_url(redirect_url) + return redirect_port + +## Client Secret to authorize (optional depending on flow) +@export_storage var client_secret: String: + set(val): + client_secret = val if val != null || val != "" else "" + emit_changed() + + +var _crypto: Crypto = Crypto.new() + +var _well_known_setting: Dictionary + +var _url_regex = RegEx.create_from_string("((https?://)?([^:/]+))(:([0-9]+))?(/.*)?") + + +func _update_redirect_url(value: String) -> void: + redirect_url = value; + var matches = _url_regex.search(value) + if matches == null: + redirect_path = "/" + redirect_port = 7170 + emit_changed() + return + + var path = matches.get_string(6) + var port = matches.get_string(5) + redirect_path = path if path != "" else "/" + redirect_port = int(port) if port != "" else 7170 + emit_changed() + + +func get_client_secret() -> String: + if client_secret == "" || client_secret == null: return "" + var value_raw = Marshalls.base64_to_raw(client_secret) + var value_bytes := _encryption_key_provider.decrypt(value_raw) + return value_bytes.get_string_from_utf8() + + +func set_client_secret(plain_secret: String) -> void: + var encrypted_value := _encryption_key_provider.encrypt(plain_secret.to_utf8_buffer()) + client_secret = Marshalls.raw_to_base64(encrypted_value) + + +func _validate_property(property: Dictionary) -> void: + if property.name == "client_secret": + if _is_client_secret_need(): + property.usage |= PROPERTY_USAGE_READ_ONLY + else: + property.usage &= ~PROPERTY_USAGE_READ_ONLY + + +func _is_client_secret_need() -> bool: + return authorization_flow == OAuth.AuthorizationFlow.AUTHORIZATION_CODE_FLOW || \ + authorization_flow == OAuth.AuthorizationFlow.CLIENT_CREDENTIALS + + +func is_valid() -> bool: + var problems = get_valididation_problems() + return problems.is_empty() + + +func get_valididation_problems() -> PackedStringArray: + var result: PackedStringArray = [] + if client_id == "" || client_id == null: + result.append("Client ID is missing") + if _is_client_secret_need() && (client_secret == "" || client_secret == null): + result.append("Client Secret is missing") + return result + + diff --git a/addons/twitcher/lib/oOuch/oauth_setting.gd.uid b/addons/twitcher/lib/oOuch/oauth_setting.gd.uid new file mode 100644 index 00000000..4a53d493 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_setting.gd.uid @@ -0,0 +1 @@ +uid://00xbijwpi8xa diff --git a/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd b/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd new file mode 100644 index 00000000..d99aade4 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd @@ -0,0 +1,115 @@ +@tool +extends EditorInspectorPlugin + +const BufferedHttpClient = preload("res://addons/twitcher/lib/http/buffered_http_client.gd") +const EncryptionKeyProvider: CryptoKeyProvider = preload("res://addons/twitcher/lib/oOuch/default_key_provider.tres") + + +func _can_handle(object: Object) -> bool: + return object is OAuthSetting + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: + if name == "well_known_url": + add_property_editor("well_known_url", WellKnownUriProperty.new()) + return true + if name == "client_id": + add_property_editor("client_secret", SecretProperty.new(), true, "Client Secret") + return false + + +class SecretProperty extends EditorProperty: + var _line_edit: LineEdit = LineEdit.new() + + + func _init() -> void: + _line_edit.secret = true + _line_edit.text_submitted.connect(_on_text_changed) + _line_edit.focus_exited.connect(_on_focus_exited) + add_child(_line_edit) + add_focusable(_line_edit) + + + func _update_property() -> void: + var secret = get_edited_object()[get_edited_property()] + + if secret == "": _line_edit.text = "" + var value_raw := Marshalls.base64_to_raw(secret) + var value_bytes := EncryptionKeyProvider.decrypt(value_raw) + _line_edit.text = value_bytes.get_string_from_utf8() + + + func _on_focus_exited() -> void: + _save() + + + func _on_text_changed(_new_text: String) -> void: + _save() + + + func _save() -> void: + var plain_value = _line_edit.text + if plain_value == "": + emit_changed(get_edited_property(), "") + return + var encrypted_value := EncryptionKeyProvider.encrypt(plain_value.to_utf8_buffer()) + emit_changed(get_edited_property(), Marshalls.raw_to_base64(encrypted_value)) + + +class WellKnownUriProperty extends EditorProperty: + var _url_regex = RegEx.create_from_string("((https?://)?([^:/]+))(:([0-9]+))?(/.*)?") + + var _container: VBoxContainer + var _well_known_url: LineEdit + var _submit: Button + var _client: BufferedHttpClient + + func _init() -> void: + _container = VBoxContainer.new() + _client = BufferedHttpClient.new() + _client.name = "OauthSettingInspectorClient" + add_child(_client) + + _well_known_url = LineEdit.new() + _well_known_url.placeholder_text = "https://id.twitch.tv/oauth2/.well-known/openid-configuration" + _well_known_url.text_changed.connect(_on_text_changed) + add_focusable(_well_known_url) + _container.add_child(_well_known_url) + + _submit = Button.new() + _submit.pressed.connect(_on_submit_clicked) + _submit.text = "Update URIs" + _container.add_child(_submit) + add_focusable(_submit) + add_child(_container) + + + func _on_text_changed(new_text: String) -> void: + emit_changed(get_edited_property(), new_text) + + + func _update_property() -> void: + _well_known_url.text = get_edited_object()[get_edited_property()] + + + func load_from_wellknown(wellknow_url: String) -> void: + var request = _client.request(wellknow_url, HTTPClient.METHOD_GET, {}, "") + var response = await _client.wait_for_request(request) as BufferedHttpClient.ResponseData + var json = JSON.parse_string(response.response_data.get_string_from_utf8()) + + var device_code = json.get("device_authorization_endpoint", "") + if device_code != "": + emit_changed(&"device_authorization_url", device_code) + var token_endpoint = json["token_endpoint"] + if token_endpoint != "": + emit_changed(&"token_url", token_endpoint) + var authorization_endpoint = json["authorization_endpoint"] + if authorization_endpoint != "": + emit_changed(&"authorization_url", authorization_endpoint) + + + func _on_submit_clicked() -> void: + _submit.disabled = true + var wellknownurl = _well_known_url.text if _well_known_url.text != "" else _well_known_url.placeholder_text + await load_from_wellknown(wellknownurl) + _submit.disabled = false diff --git a/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd.uid b/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd.uid new file mode 100644 index 00000000..d2d4d7b7 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_setting_inspector.gd.uid @@ -0,0 +1 @@ +uid://bi2tjog6pfa5a diff --git a/addons/twitcher/lib/oOuch/oauth_token.gd b/addons/twitcher/lib/oOuch/oauth_token.gd new file mode 100644 index 00000000..04584e9a --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token.gd @@ -0,0 +1,162 @@ +@icon("./security-icon.svg") +@tool +extends Resource + +## Used to store and load token's and to exchange them through the code. +## Try to avoid debugging this object cause it leaks your access and refresh tokens +## Hint never store the token value as string in your code to reduce the chance +## to leak the tokens always use the getter. + +class_name OAuthToken + +static var CRYPTO: Crypto = Crypto.new() + +const APP_ACCESS_TOKEN: StringName = &"App Access Token" +const USER_ACCESS_TOKEN: StringName = &"User Access Token" + +## Key for encryption purpose to save the tokens +@export var _crypto_key_provider: CryptoKeyProvider = preload("res://addons/twitcher/lib/oOuch/default_key_provider.tres") +## Unique identifier to store multiple tokens within one config file +@export var _identifier: String = "Auth-%s" % randi_range(0, 10000) +## Storage where the tokens should be saved encrypted (multiple secrets can be put in the same file see _identifier) +@export var _cache_path: String = "user://auth.conf": + set(val): + _cache_path = val + _load_tokens() + +## Returns if its an user access token or app accesstoken +var type: StringName +var _scopes: PackedStringArray = [] +var _expire_date: int +var _config_file: ConfigFile = ConfigFile.new() + +var _access_token: String = "": + set(val): + _access_token = val + if val != "": authorized.emit() +var _refresh_token: String = "" + + +## Called when the token was resolved / accesstoken got refreshed +signal authorized + +## Update the visual representation of the scopes (don't use it for actually changing scopes it wont work!) +# Client credential flow doesn't return used scopes and I wanted to give the use feedback over the scopes +func _update_scopes(scopes: Array[StringName]) -> void: + for scope in scopes: _scopes.append(scope) + _persist_tokens() + emit_changed() + + +## Updates the data in the scopes and persist it +func update_values(access_token: String, refresh_token: String, expire_in: int, scopes: Array[String], token_type: StringName) -> void: + _expire_date = roundi(Time.get_unix_time_from_system() + expire_in) + _access_token = access_token + _refresh_token = refresh_token + _scopes = scopes + type = token_type + _persist_tokens() + emit_changed() + + +## Persists the tokesn with the expire date +func _persist_tokens(): + var encrypted_access_token: PackedByteArray = _crypto_key_provider.encrypt(_access_token.to_utf8_buffer()) + var encrypted_refresh_token: PackedByteArray = _crypto_key_provider.encrypt(_refresh_token.to_utf8_buffer()) + _config_file.load(_cache_path) + _config_file.set_value(_identifier, "expire_date", _expire_date) + _config_file.set_value(_identifier, "type", type) + _config_file.set_value(_identifier, "access_token", Marshalls.raw_to_base64(encrypted_access_token)) + _config_file.set_value(_identifier, "refresh_token", Marshalls.raw_to_base64(encrypted_refresh_token)) + _config_file.set_value(_identifier, "scopes", ",".join(_scopes)) + var err: Error = _config_file.save(_cache_path) + if err != OK: push_error("Couldn't save tokens cause of ", error_string(err)) + + +## Loads the tokens and returns the information if the file got created +func _load_tokens() -> bool: + var status: Error = _config_file.load(_cache_path) + if status == OK && _config_file.has_section(_identifier): + _expire_date = _config_file.get_value(_identifier, "expire_date", 0) + var encrypted_access_token: PackedByteArray = Marshalls.base64_to_raw(_config_file.get_value(_identifier, "access_token")) + var encrypted_refresh_token: PackedByteArray = Marshalls.base64_to_raw(_config_file.get_value(_identifier, "refresh_token")) + _access_token = _crypto_key_provider.decrypt(encrypted_access_token).get_string_from_utf8() + _refresh_token = _crypto_key_provider.decrypt(encrypted_refresh_token).get_string_from_utf8() + type = _config_file.get_value(_identifier, "type", &"") + _scopes = _config_file.get_value(_identifier, "scopes", "").split(",", false) + emit_changed() + return true + return false + + +func remove_tokens() -> void: + var status: Error = _config_file.load(_cache_path) + if status == OK && _config_file.has_section(_identifier): + _access_token = "" + _refresh_token = "" + type = &"" + _expire_date = 0 + _scopes.clear() + + _config_file.erase_section(_identifier) + var err: Error = _config_file.save(_cache_path) + if err != OK: push_error("Couldn't save tokens cause of ", error_string(err)) + emit_changed() + print("%s got revoked" % _identifier) + else: + print("%s not found" % _identifier) + + +func get_refresh_token() -> String: + return _refresh_token + + +func get_access_token() -> String: + if not is_token_valid(): await authorized + return _access_token + + +func get_scopes() -> PackedStringArray: + return _scopes + + +## The unix timestamp when the token is expiring +func get_expiration() -> int: + return _expire_date + + +func get_expiration_readable() -> String: + if _expire_date == 0: + return "Not available" + return Time.get_datetime_string_from_unix_time(_expire_date, true) + + +func invalidate() -> void: + _expire_date = 0 + _refresh_token = "" + _access_token = "" + _scopes = [] + emit_changed() + + +## Does this accesstoken has a refresh token +func has_refresh_token() -> bool: + return _refresh_token != "" && _refresh_token != null + + +## Checks if the access token is still valid +func is_token_valid() -> bool: + var current_time: float = Time.get_unix_time_from_system() + return current_time < _expire_date + + +func _to_string() -> String: + return "<%s#%s>" % [_identifier, get_instance_id()] + + +## Get all token names within a config file +static func get_identifiers(cache_file: String) -> PackedStringArray: + var _config_file: ConfigFile = ConfigFile.new() + var status: Error = _config_file.load(cache_file) + if status != OK: return [] + return _config_file.get_sections() diff --git a/addons/twitcher/lib/oOuch/oauth_token.gd.uid b/addons/twitcher/lib/oOuch/oauth_token.gd.uid new file mode 100644 index 00000000..64572cf4 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token.gd.uid @@ -0,0 +1 @@ +uid://b52xp7c23ucfk diff --git a/addons/twitcher/lib/oOuch/oauth_token_handler.gd b/addons/twitcher/lib/oOuch/oauth_token_handler.gd new file mode 100644 index 00000000..9fb318fe --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_handler.gd @@ -0,0 +1,261 @@ +@icon("./security-icon.svg") +@tool +extends Node + +class_name OAuthTokenHandler + +const OAuthHTTPClient = preload("res://addons/twitcher/lib/http/buffered_http_client.gd") +const OAuthDeviceCodeResponse = preload("./oauth_device_code_response.gd") + +## Handles refreshing and resolving access and refresh tokens. + +const HEADERS = { + "Accept": "*/*", + "Content-Type": "application/x-www-form-urlencoded" +} +const SECONDS_TO_CHECK_EARLIER = 60 + +## Called when new access token is available +signal token_resolved(tokens: OAuthToken) + +## Called when token can't be refreshed cause auth was removed or refresh token expired +signal unauthenticated() + +## Where to get the tokens from +@export var oauth_setting: OAuthSetting + +## Holds the current set of tokens +@export var token: OAuthToken: set = _update_token + +## Client to request new tokens +var _http_client : OAuthHTTPClient + +## Is currently requesting tokens +var _requesting_token: bool = false + +## Timer to refresh tokens +var _expiration_check_timer: Timer + + +func _ready() -> void: + _http_client = OAuthHTTPClient.new() + _http_client.name = "OAuthTokenClient" + add_child(_http_client) + + _expiration_check_timer = Timer.new() + _expiration_check_timer.name = "ExpirationCheck" + _expiration_check_timer.timeout.connect(refresh_tokens) + add_child(_expiration_check_timer) + update_expiration_check() + + +func _enter_tree() -> void: + if not is_instance_valid(token): + token = OAuthToken.new() + else: + token.changed.connect(update_expiration_check) + + +func _exit_tree() -> void: + if is_instance_valid(token): + token.changed.disconnect(update_expiration_check) + + +func _update_token(val: OAuthToken) -> void: + if is_instance_valid(token) and is_inside_tree(): + token.changed.disconnect(update_expiration_check) + token = val + if is_instance_valid(token) and is_inside_tree(): + token.changed.connect(update_expiration_check) + + +func update_expiration_check() -> void: + var current_time: float = Time.get_unix_time_from_system() + var expiration: int = token.get_expiration() + if expiration == 0: + logDebug("Disable automate use of refresh token") + _expiration_check_timer.stop() + return + _expiration_check_timer.start(expiration - current_time - SECONDS_TO_CHECK_EARLIER) + logDebug("token got updated -> update timer to next refresh of token (%s) in %s seconds" % [token, roundf(_expiration_check_timer.wait_time)]) + + +## Checks if tokens expires and starts refreshing it. (called often hold footprintt small) +func _check_token_refresh() -> void: + if _requesting_token: return + + if token_needs_refresh(): + logInfo("Token (%s) needs refresh" % token) + refresh_tokens() + + +## Requests the tokens +func request_token(grant_type: String, auth_code: String = "") -> OAuthToken: + if _requesting_token: + await token_resolved + return token + + _requesting_token = true + logInfo("Request token (for %s) via '%s'" % [token, grant_type]) + var request_params: Array[String] = [ + "grant_type=%s" % grant_type, + "client_id=%s" % oauth_setting.client_id, + "client_secret=%s" % oauth_setting.get_client_secret() + ] + + if auth_code != "": + request_params.append("code=%s" % auth_code) + if grant_type == "authorization_code": + request_params.append("&redirect_uri=%s" % oauth_setting.redirect_url) + + var request_body: String = "&".join(request_params) + var request: BufferedHTTPClient.RequestData = _http_client.request(oauth_setting.token_url, \ + HTTPClient.METHOD_POST, HEADERS, request_body) + await _handle_token_request(request) + _requesting_token = false + return token + + +func request_device_token(device_code_repsonse: OAuthDeviceCodeResponse, scopes: String, grant_type: String = "urn:ietf:params:oauth:grant-type:device_code") -> void: + if _requesting_token: return + _requesting_token = true + logInfo("request token (for %s) via urn:ietf:params:oauth:grant-type:device_code" % token) + var parameters: Array[String] = [ + "client_id=%s" % oauth_setting.client_id, + "grant_type=%s" % grant_type, + "device_code=%s" % device_code_repsonse.device_code, + "scopes=%s" % scopes + ] + + var request_body: String = "&".join(parameters) + + # Time when the code is expired and we don't poll anymore + var expire_data = Time.get_unix_time_from_system() + device_code_repsonse.expires_in + + while expire_data > Time.get_unix_time_from_system(): + var request: BufferedHTTPClient.RequestData = _http_client.request(oauth_setting.token_url, HTTPClient.METHOD_POST, HEADERS, request_body) + var response = await _http_client.wait_for_request(request) + var response_string: String = response.response_data.get_string_from_utf8() + var response_data = JSON.parse_string(response_string) + if response.response_code == 200: + _update_tokens_from_response(response_data) + _requesting_token = false + return + elif response.response_code == 400 && response_string.contains("authorization_pending"): + # Awaits for this amount of time until retry + await get_tree().create_timer(device_code_repsonse.interval, true, false, true).timeout + elif response.response_code == 400: + unauthenticated.emit() + _requesting_token = false + return + + # Handle Timeout + unauthenticated.emit() + _requesting_token = false + + +## Uses the refresh token if possible to refresh all tokens +func refresh_tokens() -> void: + if not oauth_setting.is_valid(): + logError("Try to refresh token (%s) but oauth settings are invalid. Can't refresh token." % token) + _expiration_check_timer.stop() + return + + if _requesting_token: return + _requesting_token = true + logInfo("use refresh (%s) token" % token) + if token.has_refresh_token(): + var request_body: String = "client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token" % \ + [oauth_setting.client_id, oauth_setting.get_client_secret(), token.get_refresh_token()] + var request: BufferedHTTPClient.RequestData = _http_client.request(oauth_setting.token_url, \ + HTTPClient.METHOD_POST, HEADERS, request_body) + if await _handle_token_request(request): + logInfo("token (%s) got refreshed" % token) + else: + unauthenticated.emit() + else: + unauthenticated.emit() + _requesting_token = false + + +## Gets information from the response and update values returns true when success otherwise false +func _handle_token_request(request: OAuthHTTPClient.RequestData) -> bool: + var response = await _http_client.wait_for_request(request) + var response_string = response.response_data.get_string_from_utf8() + var result = JSON.parse_string(response_string) + if response.response_code == 200: + _update_tokens_from_response(result) + return true + else: + # Reset expiration cause token wasn't refreshed correctly. + token.invalidate() + logError("token (for %s) could not be fetched ResponseCode %s / Body %s" % [token, response.response_code, response_string]) + return false + + +func _update_tokens_from_response(result: Dictionary): + var scopes: Array[String] = [] + for scope in result.get("scope", []): scopes.append(scope) + var type: StringName = &"" + if oauth_setting.authorization_flow == OAuth.AuthorizationFlow.CLIENT_CREDENTIALS: + type = OAuthToken.APP_ACCESS_TOKEN + else: + type = OAuthToken.USER_ACCESS_TOKEN + + update_tokens(result["access_token"], \ + result.get("refresh_token", ""), \ + result.get("expires_in", -1), \ + scopes, + type) + + +## Updates the token. Result is the response data of an token request. +func update_tokens(access_token: String, refresh_token: String = "", expires_in: int = -1, scopes: Array[String] = [], type: StringName = &""): + token.update_values(access_token.trim_prefix(OAuth.DEBUGGER_PROTECTION), refresh_token, expires_in, scopes, type) + token_resolved.emit(token) + logInfo("token (%s) resolved" % token) + + +func get_token_expiration() -> String: + return Time.get_datetime_string_from_unix_time(token._expire_date) + + +## Checks if the token are valud +func is_token_valid() -> bool: + return token.is_token_valid() + + +## Checks if the token is expired and can be refreshed +func token_needs_refresh() -> bool: + return !token.is_token_valid() && token.has_refresh_token() + + +func get_access_token() -> String: return await token.get_access_token() + + +func has_refresh_token() -> bool: return token.has_refresh_token() + + +func get_scopes() -> PackedStringArray: return token.get_scopes() + + +# === LOGGER === +static var logger: Dictionary = {} + + +static func set_logger(error: Callable, info: Callable, debug: Callable) -> void: + logger.debug = debug + logger.info = info + logger.error = error + + +static func logDebug(text: String) -> void: + if logger.has("debug"): logger.debug.call(text) + + +static func logInfo(text: String) -> void: + if logger.has("info"): logger.info.call(text) + + +static func logError(text: String) -> void: + if logger.has("error"): logger.error.call(text) diff --git a/addons/twitcher/lib/oOuch/oauth_token_handler.gd.uid b/addons/twitcher/lib/oOuch/oauth_token_handler.gd.uid new file mode 100644 index 00000000..d95df45f --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_handler.gd.uid @@ -0,0 +1 @@ +uid://bsfi3u26qkfrc diff --git a/addons/twitcher/lib/oOuch/oauth_token_info.gd b/addons/twitcher/lib/oOuch/oauth_token_info.gd new file mode 100644 index 00000000..e0e7ca8e --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_info.gd @@ -0,0 +1,90 @@ +@tool +extends Node + +signal revoked + +@export var token: OAuthToken: set = _update_token + +@onready var title: Label = %Title +@onready var token_valid_value: Label = %TokenValidValue +@onready var refresh_token_value: CheckBox = %RefreshTokenValue +@onready var token_scope_value: Node = %TokenScopeValue +@onready var reload_button: Button = %ReloadButton +@onready var revoke_button: Button = %RevokeButton +@onready var token_type: Label = %TokenType + + +func _ready() -> void: + if token == null: + _reset_token() + return + update_token_view() + revoke_button.pressed.connect(_on_revoke_pressed) + reload_button.pressed.connect(_on_reload_pressed) + + +func _enter_tree() -> void: + if is_instance_valid(token): + token.changed.connect(_on_token_changed) + + +func _exit_tree() -> void: + if is_instance_valid(token): + token.changed.disconnect(_on_token_changed) + + +func _update_token(val: OAuthToken) -> void: + if is_instance_valid(token): + token.changed.disconnect(_on_token_changed) + token = val + if is_instance_valid(token) and is_inside_tree(): + token.changed.connect(_on_token_changed) + + +func update_token_view() -> void: + title.text = token._identifier + token_valid_value.text = token.get_expiration_readable() + if token.is_token_valid(): + token_valid_value.add_theme_color_override(&"font_color", Color.GREEN) + else: + token_valid_value.add_theme_color_override(&"font_color", Color.RED) + + if token.has_refresh_token(): + refresh_token_value.text = "Available" + refresh_token_value.add_theme_color_override(&"font_color", Color.GREEN) + refresh_token_value.button_pressed = true + else: + refresh_token_value.text = "Not Available" + refresh_token_value.add_theme_color_override(&"font_color", Color.YELLOW) + refresh_token_value.button_pressed = false + + token_type.text = token.type + + for scope in token.get_scopes(): + var scope_name = Label.new() + scope_name.text = scope + token_scope_value.add_child(scope_name) + revoke_button.disabled = false + + +func _on_revoke_pressed() -> void: + token.remove_tokens() + _reset_token() + + +func _on_reload_pressed() -> void: + _reset_token() + token._load_tokens() + + +func _reset_token() -> void: + title.text = "" + token_valid_value.text = "" + refresh_token_value.button_pressed = false + revoke_button.disabled = true + for child in token_scope_value.get_children(): + child.queue_free() + + +func _on_token_changed() -> void: + update_token_view() diff --git a/addons/twitcher/lib/oOuch/oauth_token_info.gd.uid b/addons/twitcher/lib/oOuch/oauth_token_info.gd.uid new file mode 100644 index 00000000..7839f634 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_info.gd.uid @@ -0,0 +1 @@ +uid://cht8c01quk1mb diff --git a/addons/twitcher/lib/oOuch/oauth_token_info.tscn b/addons/twitcher/lib/oOuch/oauth_token_info.tscn new file mode 100644 index 00000000..c94e5f78 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_info.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=2 format=3 uid="uid://6d2jst8ga4le"] + +[ext_resource type="Script" uid="uid://cht8c01quk1mb" path="res://addons/twitcher/lib/oOuch/oauth_token_info.gd" id="1_xfn6u"] + +[node name="TokenInfo" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 +script = ExtResource("1_xfn6u") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Title" type="Label" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"HeaderLarge" + +[node name="GridContainer" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="TokenValidTitle" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Token Valid:" + +[node name="TokenValidValue" type="Label" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="RefreshTokenTitle" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Refresh Token Available:" + +[node name="RefreshTokenValue" type="CheckBox" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true + +[node name="TokenTypeTitle" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Type:" + +[node name="TokenType" type="Label" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="TokenScopeTitle" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Token Scope:" + +[node name="TokenScopeValue" type="VBoxContainer" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="ReloadButton" type="Button" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Reloads the token from file use to update the infos when the token was fetched by an application run." +text = "Reload Info" + +[node name="RevokeButton" type="Button" parent="VBoxContainer/GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Will remove the cached access token / refresh token to fetch a new one" +disabled = true +text = "Revoke Token" diff --git a/addons/twitcher/lib/oOuch/oauth_token_inspector.gd b/addons/twitcher/lib/oOuch/oauth_token_inspector.gd new file mode 100644 index 00000000..31b404ec --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_inspector.gd @@ -0,0 +1,13 @@ +@tool +extends EditorInspectorPlugin + +var token_info_scene: PackedScene = preload("res://addons/twitcher/lib/oOuch/oauth_token_info.tscn") + +func _can_handle(object: Object) -> bool: + return object is OAuthToken + + +func _parse_begin(object: Object) -> void: + var token_info = token_info_scene.instantiate() + token_info.token = object + add_custom_control(token_info) diff --git a/addons/twitcher/lib/oOuch/oauth_token_inspector.gd.uid b/addons/twitcher/lib/oOuch/oauth_token_inspector.gd.uid new file mode 100644 index 00000000..be12b4d9 --- /dev/null +++ b/addons/twitcher/lib/oOuch/oauth_token_inspector.gd.uid @@ -0,0 +1 @@ +uid://djylbydr6sh64 diff --git a/addons/twitcher/lib/oOuch/scope-icon.svg b/addons/twitcher/lib/oOuch/scope-icon.svg new file mode 100644 index 00000000..8e1e6b38 --- /dev/null +++ b/addons/twitcher/lib/oOuch/scope-icon.svg @@ -0,0 +1,64 @@ + + + + + + + + + diff --git a/addons/twitcher/lib/oOuch/scope-icon.svg.import b/addons/twitcher/lib/oOuch/scope-icon.svg.import new file mode 100644 index 00000000..0b08d48f --- /dev/null +++ b/addons/twitcher/lib/oOuch/scope-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdu2sqj8cfh80" +path="res://.godot/imported/scope-icon.svg-760ec336caee34c24da7cfc19dbbd818.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/lib/oOuch/scope-icon.svg" +dest_files=["res://.godot/imported/scope-icon.svg-760ec336caee34c24da7cfc19dbbd818.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/lib/oOuch/security-icon.svg b/addons/twitcher/lib/oOuch/security-icon.svg new file mode 100644 index 00000000..b142ebc6 --- /dev/null +++ b/addons/twitcher/lib/oOuch/security-icon.svg @@ -0,0 +1,92 @@ + + + + + + + + + + diff --git a/addons/twitcher/lib/oOuch/security-icon.svg.import b/addons/twitcher/lib/oOuch/security-icon.svg.import new file mode 100644 index 00000000..7c82bb27 --- /dev/null +++ b/addons/twitcher/lib/oOuch/security-icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ctgrmafl803p0" +path="res://.godot/imported/security-icon.svg-8d6ec29771c7aa01a3aece2fa8908f4c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/twitcher/lib/oOuch/security-icon.svg" +dest_files=["res://.godot/imported/security-icon.svg-8d6ec29771c7aa01a3aece2fa8908f4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/twitcher/logger/twitch_logger.gd b/addons/twitcher/logger/twitch_logger.gd new file mode 100644 index 00000000..44f4f246 --- /dev/null +++ b/addons/twitcher/logger/twitch_logger.gd @@ -0,0 +1,64 @@ +@tool +extends RefCounted + +## Logger class that can be enabled and disabled. (Works best with tool scripts) +class_name TwitchLogger + +## Name of the logger that will be shown in the logs +var context_name: String +var suffix: String +var enabled : bool +var debug: bool +var color: String + + +func _init(ctx_name: String, active: bool = false, should_debug: bool = false) -> void: + context_name = ctx_name + enabled = active + debug = should_debug + color = string_to_hex_color(ctx_name) + TwitchLoggerManager.register(self) + + +func is_enabled() -> bool: + return enabled + + +func set_enabled(status: bool) -> void: + enabled = status + + +func set_suffix(s: String) -> void: + suffix = "-" + s + + +## log a message on info level +func i(text: String): + if is_enabled(): print_rich("%s I[color=%s][%s%s] %s[/color]" % [Time.get_ticks_msec(), color, context_name, suffix, text]) + #else: print(context_name, " is not enabled") + + +## log a message on error level +func e(text: String): + if is_enabled(): print_rich("%s E[b][color=%s][%s%s] %s[/color][/b]" % [Time.get_ticks_msec(), color, context_name, suffix, text]) + #else: print(context_name, " is not enabled") + + +func d(text: String): + if is_enabled() && debug: print_rich("%s D[i][color=%s][%s%s] %s[/color][/i]" % [Time.get_ticks_msec(), color, context_name, suffix, text]) + + +func string_to_hex_color(text: String) -> String: + # Hash the text to generate a unique integer + var hash_value = text.hash() + var r = hash_value & 0xFF + var g = (hash_value >> 8) & 0xFF + var b = (hash_value >> 16) & 0xFF + const brighten_factor = 1.5 + r = clamp(r * brighten_factor, 0, 255) + g = clamp(g * brighten_factor, 0, 255) + b = clamp(b * brighten_factor, 0, 255) + var red = "%02x" % r + var green = "%02x" % g + var blue = "%02x" % b + return "#" + red + green + blue diff --git a/addons/twitcher/logger/twitch_logger.gd.uid b/addons/twitcher/logger/twitch_logger.gd.uid new file mode 100644 index 00000000..084fc379 --- /dev/null +++ b/addons/twitcher/logger/twitch_logger.gd.uid @@ -0,0 +1 @@ +uid://dvxh6uhvwiuyj diff --git a/addons/twitcher/logger/twitch_logger_manager.gd b/addons/twitcher/logger/twitch_logger_manager.gd new file mode 100644 index 00000000..f81b9638 --- /dev/null +++ b/addons/twitcher/logger/twitch_logger_manager.gd @@ -0,0 +1,16 @@ +@tool +extends RefCounted + +## Couples the logger to the enabled state of the settings. +class_name TwitchLoggerManager + +static var log_registry : Dictionary = {} + +## Register the logger and set the enabled state +static func register(logger: TwitchLogger) -> void: + log_registry[logger.context_name] = logger + var property = TwitchProperty.new("twitcher/logs/%s" % logger.context_name, "off").as_select(["off", "info", "debug"]) + if property.get_val() != "off": + logger.set_enabled(true) + if property.get_val() == "debug": + logger.debug = true diff --git a/addons/twitcher/logger/twitch_logger_manager.gd.uid b/addons/twitcher/logger/twitch_logger_manager.gd.uid new file mode 100644 index 00000000..dfcb91fb --- /dev/null +++ b/addons/twitcher/logger/twitch_logger_manager.gd.uid @@ -0,0 +1 @@ +uid://tyuiry12mvky diff --git a/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd b/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd new file mode 100644 index 00000000..3530fcd6 --- /dev/null +++ b/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd @@ -0,0 +1,52 @@ +@tool +extends EditorImportPlugin + +class_name GifImporterImagemagick + +enum Presets { DEFAULT } + +func _get_importer_name() -> String: + return "kani_dev.imagemagick" + +func _get_visible_name() -> String: + return "SpriteFrames (ImageMagick)" + +func _get_recognized_extensions() -> PackedStringArray: + return ["gif", "webp"] + +func _get_save_extension() -> String: + return "res" + +func _get_resource_type() -> String: + return "SpriteFrames" + +func _get_priority() -> float: + return 100.0 + +func _get_preset_count() -> int: + return Presets.size() + +func _get_preset_name(preset_index: int) -> String: + return "Default" + +func _get_import_options(path: String, preset_index: int) -> Array[Dictionary]: + return [] + +func _get_import_order() -> int: + return 0 + +func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool: + return true + +func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error: + var dumper = ImageMagickConverter.new() + + var tex = await dumper.dump_and_convert(source_file, [], "") + if tex: + return ResourceSaver.save( + tex, + "%s.%s" % [save_path, _get_save_extension()], + ResourceSaver.SaverFlags.FLAG_COMPRESS + ) + push_error("failed to import %s" % source_file) + return OK; diff --git a/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd.uid b/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd.uid new file mode 100644 index 00000000..6660064c --- /dev/null +++ b/addons/twitcher/media/imagemagick/gif_importer_imagemagick.gd.uid @@ -0,0 +1 @@ +uid://chveatddlbp1x diff --git a/addons/twitcher/media/imagemagick/image_magick_converter.gd b/addons/twitcher/media/imagemagick/image_magick_converter.gd new file mode 100644 index 00000000..cc24ee60 --- /dev/null +++ b/addons/twitcher/media/imagemagick/image_magick_converter.gd @@ -0,0 +1,174 @@ +@tool +extends RefCounted + +class_name ImageMagickConverter + +static var _log: TwitchLogger = TwitchLogger.new("ImageMagickConverter") + +## Current conversion in progress (key: path | value: mutex) +var converting: Dictionary = {} +var fallback_texture: Texture2D = preload("res://addons/twitcher/assets/fallback_texture.tres") +## Path to the imagemagic executable for example 'magick' when its in windows path +var imagemagic_path: String + +static var delete_mutex: Mutex = Mutex.new() +static var folder_to_delete: Array[String] = [] + +## After that amount of files it starts to delete them +const DELETE_COUNT = 10 + +## Converts a packed byte array to a SpirteFrames and writes it out to the destination path +## +## The byte array must represent an animated gif, webp, or any imagemagick supported format +## it dumps it into a binary resource consisting of PNG frames. +## +## The resource is automatically added to the ResourceLoader cache as the input path value +func dump_and_convert(path: String, buffer_in: PackedByteArray = [], output = "%s.res" % path, parallel = false) -> SpriteFrames: + var thread: Thread = Thread.new() + var buffer: PackedByteArray = buffer_in.duplicate() + var mutex: Mutex + if parallel: + mutex = converting.get(path, Mutex.new()) + converting[path] = mutex + + var err = thread.start(_do_work.bind(path, buffer, output, mutex)) + assert(err == OK, "could not start thread") + + # don't block the main thread while loading + while not thread.is_started() or thread.is_alive(): + await Engine.get_main_loop().process_frame + + var tex: SpriteFrames = thread.wait_to_finish() + if parallel: + mutex.unlock() + converting.erase(path) + + if not output.is_empty(): + _save_converted_file(tex, output) + return tex + + +func _do_work(path: String, buffer: PackedByteArray, output: String, mutex: Mutex) -> SpriteFrames: + if mutex != null: + mutex.lock() + # load from cache if another thread already completed converting this same resource + if not output.is_empty() and ResourceLoader.has_cached(output): + return ResourceLoader.load(output) + + # dump the buffer + if FileAccess.file_exists(path): + _log.i("File found at %s, loading it instead of using the buffer." % path) + buffer = FileAccess.get_file_as_bytes(path) + else: + DirAccess.make_dir_recursive_absolute(path.get_base_dir()) + var f = FileAccess.open(path, FileAccess.WRITE) + if f == null: + _log.e("Can't open file %s cause of %s" %[ path, FileAccess.get_open_error()]) + f.store_buffer(buffer) + f.close() + + var frame_delays: Array[int] = _get_frame_delay(path) + var folder_path: String = _create_temp_filename() + if not _extract_images(path, folder_path): + return _create_fallback_texture() + var sprite_frames: SpriteFrames = _build_frames(folder_path, frame_delays) + if not output.is_empty(): + sprite_frames.take_over_path(output) + + # delete the temp directory + delete_mutex.lock() + folder_to_delete.append(folder_path) + delete_mutex.unlock() + + _cleanup() + return sprite_frames + + +## Saves the texture to the output path +func _save_converted_file(tex: SpriteFrames, output: String): + if not output.is_empty() and tex: + ResourceSaver.save(tex, output, ResourceSaver.SaverFlags.FLAG_COMPRESS) + tex.take_over_path(output) + + +func _create_unique_key(length: int = 8) -> String: + var uniq = "" + for i in range(length): + uniq += "%d" % [randi() % 10] + return uniq + + +## Creates a folder to store the extracted images (needs the / at the end!) +func _create_temp_filename() -> String: + var folder_path: String = "" + var uniq = _create_unique_key() + if Engine.is_editor_hint(): + folder_path = "res://.godot/magick_tmp/%s_%d/" % [uniq, Time.get_unix_time_from_system()] + else: + folder_path = "user://.magick_tmp/%s_%d/" % [uniq, Time.get_unix_time_from_system()] + + _log.i("Create temp folder") + DirAccess.make_dir_recursive_absolute(folder_path) + return folder_path + + +## Extracts all delays from the file in seconds +func _get_frame_delay(file: String) -> Array[int]: + var out = [] + var glob_path = ProjectSettings.globalize_path(file) + OS.execute(imagemagic_path, [ glob_path, "-format", "%T\\n", "info:" ], out) + var frame_delays: Array[int] = [] + for delay in out[0].split("\n"): + # convert x100 to x1000(ms) + frame_delays.append(delay.to_int() * 10) + return frame_delays + + +## Extracts all images from the file and saves them to folder path +func _extract_images(file: String, target_folder: String) -> bool: + var out = [] + var glob_file_path = ProjectSettings.globalize_path(file) + var glob_extracted_file_path = ProjectSettings.globalize_path(target_folder + "%04d.png") + var code = OS.execute(imagemagic_path, [ "convert", "-coalesce", glob_file_path, glob_extracted_file_path ], out, true) + if code != 0: + _log.e("unable to convert: %s" % "\n".join(out)) + return false + return true + + +func _create_fallback_texture(): + var sprite_frames = SpriteFrames.new() + sprite_frames.add_frame(&"default", fallback_texture) + return sprite_frames + + +func _build_frames(folder_path: String, frame_delays: Array[int]): + _log.i("Build Frames") + var frames = DirAccess.get_files_at(folder_path) + if len(frames) == 0: + return _create_fallback_texture() + + var sprite_frames: SpriteFrames = SpriteFrames.new() + for filepath in frames: + var idx = filepath.substr(0, filepath.rfind(".")).to_int() + var delay = frame_delays[idx] / 1000.0 + var image = Image.new() + var error = image.load(folder_path + filepath) + if error != OK: + return _create_fallback_texture() + + var frame = ImageTexture.create_from_image(image) + sprite_frames.add_frame(&"default", frame, delay) + sprite_frames.set_animation_speed(&"default", 1) + return sprite_frames + + +## Cleans after DELETE_COUNT amount of folder entries +func _cleanup(force: bool = false): + if folder_to_delete.size() % DELETE_COUNT == 0 || force: + delete_mutex.lock() + for folder in folder_to_delete: + var glob_folder = ProjectSettings.globalize_path(folder) + OS.move_to_trash(glob_folder) + folder_to_delete.clear() + delete_mutex.unlock() diff --git a/addons/twitcher/media/imagemagick/image_magick_converter.gd.uid b/addons/twitcher/media/imagemagick/image_magick_converter.gd.uid new file mode 100644 index 00000000..8c87f599 --- /dev/null +++ b/addons/twitcher/media/imagemagick/image_magick_converter.gd.uid @@ -0,0 +1 @@ +uid://dydhjmb7b0ob0 diff --git a/addons/twitcher/media/magic_image_transformer.gd b/addons/twitcher/media/magic_image_transformer.gd new file mode 100644 index 00000000..2dd70aa6 --- /dev/null +++ b/addons/twitcher/media/magic_image_transformer.gd @@ -0,0 +1,44 @@ +@icon("res://addons/twitcher/assets/media-loader-icon.svg") +@tool +extends TwitchImageTransformer + +## A image transformer that uses a thirdparty program called ImageMagick +## (https://imagemagick.org/script/download.php) to transform GIF's and similar files into +## SpriteFrames. Battleproof very stable but uses an external program. +class_name MagicImageTransformer + + +## Path to the imagemagick program on windows its normally just 'magick' when it is available in the PATH +@export_global_file var imagemagic_path: String: + set = update_imagemagic_path + +var supported: bool +var converter: ImageMagickConverter + +func is_supporting_animation() -> bool: + return true + + +func update_imagemagic_path(path: String) -> void: + if imagemagic_path == path: return + imagemagic_path = path + if imagemagic_path != "": + var out = [] + var err = OS.execute(imagemagic_path, ['-version'], out) + if err == OK: + converter = ImageMagickConverter.new() + converter.fallback_texture = fallback_texture + converter.imagemagic_path = imagemagic_path + _log.i("Imagemagic detected: %s use it to transform gif/webm" % [ imagemagic_path ]) + supported = true + return + _log.i("Imagemagic at '%s' path was not detected or has a wrong result code: %s \n %s" % [ imagemagic_path, err, "\n".join(out) ]) + supported = false + + +func is_supported() -> bool: + return supported + + +func convert_image(path: String, buffer_in: PackedByteArray, output_path: String) -> SpriteFrames: + return await converter.dump_and_convert(path, buffer_in, output_path) diff --git a/addons/twitcher/media/magic_image_transformer.gd.uid b/addons/twitcher/media/magic_image_transformer.gd.uid new file mode 100644 index 00000000..eacd9f8c --- /dev/null +++ b/addons/twitcher/media/magic_image_transformer.gd.uid @@ -0,0 +1 @@ +uid://dqd4kovdhhy8o diff --git a/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd b/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd new file mode 100644 index 00000000..c8afe250 --- /dev/null +++ b/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd @@ -0,0 +1,53 @@ +# Derived from https://github.com/jegor377/godot-gdgifexporter + +@tool +extends EditorImportPlugin + +class_name GifImporterNative + +enum Presets { DEFAULT } + +func _get_importer_name() -> String: + return "gif.animated.texture.plugin" + +func _get_visible_name() -> String: + return "Sprite Frames (Native)" + +func _get_recognized_extensions() -> PackedStringArray: + return ["gif"] + +func _get_save_extension() -> String: + return "res" + +func _get_resource_type() -> String: + return "SpriteFrames" + +func _get_priority() -> float: + return 90.0; + +func _get_preset_count() -> int: + return Presets.size() + +func _get_preset_name(preset_index: int) -> String: + return "Default" + +func _get_import_options(path: String, preset_index: int) -> Array[Dictionary]: + return [] + +func _get_import_order() -> int: + return 0 + +func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool: + return true + +func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error: + var reader = GifReader.new() + var tex = reader.read(source_file) + if tex == null: + return FAILED + var filename = save_path + "." + _get_save_extension() + return ResourceSaver.save( + tex, + "%s.%s" % [save_path, _get_save_extension()], + ResourceSaver.SaverFlags.FLAG_COMPRESS + ) diff --git a/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd.uid b/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd.uid new file mode 100644 index 00000000..74b424a4 --- /dev/null +++ b/addons/twitcher/media/native/GIF2SpriteFramesPlugin.gd.uid @@ -0,0 +1 @@ +uid://cr3b26x6lrmf6 diff --git a/addons/twitcher/media/native/GIFReader.gd b/addons/twitcher/media/native/GIFReader.gd new file mode 100644 index 00000000..977f5bf8 --- /dev/null +++ b/addons/twitcher/media/native/GIFReader.gd @@ -0,0 +1,128 @@ +@tool +extends RefCounted + +class_name GifReader + +var lzw_module = preload("./gif-lzw/lzw.gd") +var lzw = lzw_module.new() + +func read(source_file) -> SpriteFrames: + var file = FileAccess.open(source_file, FileAccess.READ) + if file == null: + return null + var data = file.get_buffer(file.get_length()) + file.close() + return load_gif(data) + +func load_gif(data): + var pos = 0 + # Header 'GIF89a' + pos = pos + 6 + # Logical Screen Descriptor + var width = get_int(data, pos) + var height = get_int(data, pos + 2) + var packed_info = data[pos + 4] + var background_color_index = data[pos + 5] + pos = pos + 7 + # Global color table + var global_lut + if (packed_info & 0x80) != 0: + var lut_size = 1 << (1 + (packed_info & 0x07)) + global_lut = get_lut(data, pos, lut_size) + pos = pos + 3 * lut_size + # Frames + var repeat = -1 + var img = Image.new() + var frame_number = 0 + var frame_delay = -1 + var frame_anim_packed_info = -1 + var frame_transparent_color = -1 + var sprite_frame = SpriteFrames.new() + + img = Image.create(width, height, false, Image.FORMAT_RGBA8) + while pos < data.size(): + if data[pos] == 0x21: # Extension block + var ext_type = data[pos + 1] + pos = pos + 2 # 21 xx ... + match ext_type: + 0xF9: # Graphic extension + var subblock = get_subblock(data, pos) + frame_anim_packed_info = subblock[0] + frame_delay = get_int(subblock, 1) + frame_transparent_color = subblock[3] + 0xFF: # Application extension + var subblock = get_subblock(data, pos) + if subblock != null and subblock.get_string_from_ascii() == "NETSCAPE2.0": + subblock = get_subblock(data, pos + 1 + subblock.size()) + repeat = get_int(subblock, 1) + _: # Miscelaneous extension + #print("extension ", data[pos + 1]) + pass + var block_len = 0 + while data[pos + block_len] != 0: + block_len = block_len + data[pos + block_len] + 1 + pos = pos + block_len + 1 + elif data[pos] == 0x2C: # Image data + var img_left = get_int(data, pos + 1) + var img_top = get_int(data, pos + 3) + var img_width = get_int(data, pos + 5) + var img_height = get_int(data, pos + 7) + var img_packed_info = get_int(data, pos + 9) + pos = pos + 10 + # Local color table + var local_lut = global_lut + if (img_packed_info & 0x80) != 0: + var lut_size = 1 << (1 + (img_packed_info & 0x07)) + local_lut = get_lut(data, pos, lut_size) + pos = pos + 3 * lut_size + # Image data + var min_code_size = data[pos] + pos = pos + 1 + var colors = [] + for i in range(0, 1 << min_code_size): + colors.append(i) + var block = PackedByteArray() + while data[pos] != 0: + block.append_array(data.slice(pos + 1, pos + data[pos] + 1)) + pos = pos + data[pos] + 1 + pos = pos + 1 + var decompressed = lzw.decompress_lzw(block, min_code_size, colors) + var disposal = (frame_anim_packed_info >> 2) & 7 # 1 = Keep, 2 = Clear + var transparency = frame_anim_packed_info & 1 + if disposal == 2: + if transparency == 0 and background_color_index != frame_transparent_color: + img.fill(local_lut[background_color_index]) + else: + img.fill(Color(0,0,0,0)) + var p = 0 + for y in range(0, img_height): + for x in range(0, img_width): + var c = decompressed[p] + if transparency == 0 or c != frame_transparent_color: + img.set_pixel(img_left + x, img_top + y, local_lut[c]) + p = p + 1 + var frame = ImageTexture.create_from_image(img); + sprite_frame.add_frame(&"default", frame, frame_delay / 100.0); + frame_anim_packed_info = -1 + frame_transparent_color = -1 + frame_delay = -1 + frame_number = frame_number + 1 + elif data[pos] == 0x3B: # Trailer + pos = pos + 1 + sprite_frame.set_animation_speed(&"default", 1); + return sprite_frame + +func get_subblock(data: PackedByteArray, pos): + if data[pos] == 0: + return null + else: + return data.slice(pos + 1, pos + data[pos] + 1) + +func get_lut(data, pos, size): + var colors = Array() + for i in range(0, size): + colors.append(Color(data[pos + i * 3] / 255.0, data[pos + 1 + i * 3] / 255.0, data[pos + 2 + i * 3] / 255.0)) + return colors + +func get_int(data, pos): + return data[pos] + (data[pos + 1] << 8) diff --git a/addons/twitcher/media/native/GIFReader.gd.uid b/addons/twitcher/media/native/GIFReader.gd.uid new file mode 100644 index 00000000..1e9b6471 --- /dev/null +++ b/addons/twitcher/media/native/GIFReader.gd.uid @@ -0,0 +1 @@ +uid://m6k2g1678fpc diff --git a/addons/twitcher/media/native/README.md b/addons/twitcher/media/native/README.md new file mode 100644 index 00000000..bbc4572d --- /dev/null +++ b/addons/twitcher/media/native/README.md @@ -0,0 +1,2 @@ +# Known Bugs +- https://github.com/ImageMagick/ImageMagick/issues/4634 exists in this lib too. Images that are not correctly encoded makes problems diff --git a/addons/twitcher/media/native/gif-lzw/LICENSE b/addons/twitcher/media/native/gif-lzw/LICENSE new file mode 100644 index 00000000..ca5b2ad5 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Igor Santarek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd b/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd new file mode 100644 index 00000000..4656f4d3 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd @@ -0,0 +1,31 @@ +@tool +extends RefCounted + +class LSBLZWBitPacker: + var bit_index: int = 0 + var stream: int = 0 + + var chunks: PackedByteArray = PackedByteArray([]) + + func put_byte(): + chunks.append(stream & 0xff) + bit_index -= 8 + stream >>= 8 + + func write_bits(value: int, bits_count: int) -> void: + value &= (1 << bits_count) - 1 + value <<= bit_index + stream |= value + bit_index += bits_count + while bit_index >= 8: + put_byte() + + func pack() -> PackedByteArray: + if bit_index != 0: + put_byte() + return chunks + + func reset() -> void: + bit_index = 0 + stream = 0 + chunks = PackedByteArray([]) diff --git a/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd.uid b/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd.uid new file mode 100644 index 00000000..2b54c12d --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lsbbitpacker.gd.uid @@ -0,0 +1 @@ +uid://rbrgncjo0m63 diff --git a/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd b/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd new file mode 100644 index 00000000..a728a4b7 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd @@ -0,0 +1,44 @@ +@tool +extends RefCounted + +class LSBLZWBitUnpacker: + var chunk_stream: PackedByteArray + var bit_index: int = 0 + var byte: int + var byte_index: int = 0 + + func _init(_chunk_stream: PackedByteArray): + chunk_stream = _chunk_stream + get_byte() + + func get_bit(value: int, index: int) -> int: + return (value >> index) & 1 + + func set_bit(value: int, index: int) -> int: + return value | (1 << index) + + func get_byte(): + byte = chunk_stream[byte_index] + byte_index += 1 + bit_index = 0 + + func read_bits(bits_count: int) -> int: + var result: int = 0 + var result_bit_index: int = 0 + + for _i in range(bits_count): + if get_bit(byte, bit_index) == 1: + result = set_bit(result, result_bit_index) + result_bit_index += 1 + bit_index += 1 + + if chunk_stream.size() == byte_index && result_bit_index == bits_count: + return result; + + if bit_index == 8: + get_byte() + + return result + + func remove_bits(bits_count: int) -> void: + read_bits(bits_count) diff --git a/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd.uid b/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd.uid new file mode 100644 index 00000000..27936530 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lsbbitunpacker.gd.uid @@ -0,0 +1 @@ +uid://cjogpplbpf7hp diff --git a/addons/twitcher/media/native/gif-lzw/lzw.gd b/addons/twitcher/media/native/gif-lzw/lzw.gd new file mode 100644 index 00000000..d07626e2 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lzw.gd @@ -0,0 +1,213 @@ +@tool +extends RefCounted + +var lsbbitpacker = preload("./lsbbitpacker.gd") +var lsbbitunpacker = preload("./lsbbitunpacker.gd") + +class CodeEntry: + var sequence: PackedByteArray + var raw_array: PackedByteArray + + func _init(_sequence : PackedByteArray) -> void: + raw_array = _sequence + sequence = _sequence + + func add(other) -> CodeEntry: + return CodeEntry.new(raw_array + other.raw_array) + + func to_string() -> String: + var result: String = "" + for element in sequence: + result += str(element) + ", " + return result.substr(0, result.length() - 2) + + +class CodeTable: + var entries: Dictionary = {} + var counter: int = 0 + var lookup: Dictionary = {} + + func add(entry: CodeEntry) -> int: + entries[counter] = entry + lookup[entry.raw_array] = counter + counter += 1 + return counter + + func find(entry: CodeEntry) -> int: + return lookup.get(entry.raw_array, -1) + + func has_entry(entry: CodeEntry) -> bool: + return find(entry) != -1 + + func get_entry(index: int) -> CodeEntry: + return entries.get(index, null) + + func to_string() -> String: + var result: String = "CodeTable:\n" + for id in entries: + result += str(id) + ": " + entries[id].to_string() + "\n" + result += "Counter: " + str(counter) + "\n" + return result + + +func log2(value: float) -> float: + return log(value) / log(2.0) + +func get_bits_number_for(value: int) -> int: + if value == 0: + return 1 + return int(ceil(log2(value + 1))) + +func initialize_color_code_table(colors: PackedByteArray) -> CodeTable: + var result_code_table: CodeTable = CodeTable.new() + for color_id in colors: + # warning-ignore:return_value_discarded + result_code_table.add(CodeEntry.new([color_id])) + # move counter to the first available compression code index + var last_color_index: int = colors.size() - 1 + var clear_code_index: int = pow(2, get_bits_number_for(last_color_index)) + result_code_table.counter = clear_code_index + 2 + return result_code_table + + +# compression and decompression done with source: +# http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp + + +func compress_lzw(image: PackedByteArray, colors: PackedByteArray) -> Array: + # Initialize code table + var code_table: CodeTable = initialize_color_code_table(colors) + # Clear Code index is 2** + # is the amount of bits needed to write down all colors + # from color table. We use last color index because we can write + # all colors (for example 16 colors) with indexes from 0 to 15. + # Number 15 is in binary 0b1111, so we'll need 4 bits to write all + # colors down. + var last_color_index: int = colors.size() - 1 + var clear_code_index: int = pow(2, get_bits_number_for(last_color_index)) + var index_stream: PackedByteArray = image + var current_code_size: int = get_bits_number_for(clear_code_index) + var binary_code_stream = lsbbitpacker.LSBLZWBitPacker.new() + + # initialize with Clear Code + binary_code_stream.write_bits(clear_code_index, current_code_size) + + # Read first index from index stream. + var index_buffer: CodeEntry = CodeEntry.new([index_stream[0]]) + var data_index: int = 1 + # + while data_index < index_stream.size(): + # Get the next index from the index stream. + var k: CodeEntry = CodeEntry.new([index_stream[data_index]]) + data_index += 1 + # Is index buffer + k in our code table? + var new_index_buffer: CodeEntry = index_buffer.add(k) + if code_table.has_entry(new_index_buffer): # if YES + # Add k to the end of the index buffer + index_buffer = new_index_buffer + else: # if NO + # Add a row for index buffer + k into our code table + binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size) + + # We don't want to add new code to code table if we've exceeded 4095 + # index. + var last_entry_index: int = code_table.counter - 1 + if last_entry_index != 4095: + # Output the code for just the index buffer to our code stream + # warning-ignore:return_value_discarded + code_table.add(new_index_buffer) + else: + # if we exceeded 4095 index (code table is full), we should + # output Clear Code and reset everything. + binary_code_stream.write_bits(clear_code_index, current_code_size) + code_table = initialize_color_code_table(colors) + # get_bits_number_for(clear_code_index) is the same as + # LZW code size + 1 + current_code_size = get_bits_number_for(clear_code_index) + + # Detect when you have to save new codes in bigger bits boxes + # change current code size when it happens because we want to save + # flexible code sized codes + var new_code_size_candidate: int = get_bits_number_for(code_table.counter - 1) + if new_code_size_candidate > current_code_size: + current_code_size = new_code_size_candidate + + # Index buffer is set to k + index_buffer = k + # Output code for contents of index buffer + binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size) + + # output end with End Of Information Code + binary_code_stream.write_bits(clear_code_index + 1, current_code_size) + + var min_code_size: int = get_bits_number_for(clear_code_index) - 1 + + return [binary_code_stream.pack(), min_code_size] + + +# gdlint: ignore=max-line-length +func decompress_lzw(code_stream_data: PackedByteArray, min_code_size: int, colors: PackedByteArray) -> PackedByteArray: + var code_table: CodeTable = initialize_color_code_table(colors) + var index_stream: PackedByteArray = PackedByteArray([]) + var binary_code_stream = lsbbitunpacker.LSBLZWBitUnpacker.new(code_stream_data) + var current_code_size: int = min_code_size + 1 + var clear_code_index: int = pow(2, min_code_size) + + # CODE is an index of code table, {CODE} is sequence inside + # code table with index CODE. The same goes for PREVCODE. + + # let CODE be the first code in the code stream + var code: int = binary_code_stream.read_bits(current_code_size) + + # Remove first Clear Code from stream. We don't need it. + if code == clear_code_index: + code = binary_code_stream.read_bits(current_code_size); + + # output {CODE} to index stream + index_stream.append_array(code_table.get_entry(code).sequence) + # set PREVCODE = CODE + var prevcode: int = code + # + while true: + # let CODE be the next code in the code stream + code = binary_code_stream.read_bits(current_code_size) + # Detect Clear Code. When detected reset everything and get next code. + if code == clear_code_index: + code_table = initialize_color_code_table(colors) + current_code_size = min_code_size + 1 + code = binary_code_stream.read_bits(current_code_size) + index_stream.append_array(code_table.get_entry(code).sequence) + prevcode = code + continue + elif code == clear_code_index + 1: # Stop when detected EOI Code. + break + # is CODE in the code table? + var code_entry: CodeEntry = code_table.get_entry(code) + if code_entry != null: # if YES + # output {CODE} to index stream + index_stream.append_array(code_entry.sequence) + # let k be the first index in {CODE} + var k: CodeEntry = CodeEntry.new([code_entry.sequence[0]]) + # warning-ignore:return_value_discarded + # add {PREVCODE} + k to the code table + code_table.add(code_table.get_entry(prevcode).add(k)) + # set PREVCODE = CODE + prevcode = code + else: # if NO + # let k be the first index of {PREVCODE} + var prevcode_entry: CodeEntry = code_table.get_entry(prevcode) + var k: CodeEntry = CodeEntry.new([prevcode_entry.sequence[0]]) + # output {PREVCODE} + k to index stream + index_stream.append_array(prevcode_entry.add(k).sequence) + # add {PREVCODE} + k to code table + # warning-ignore:return_value_discarded + code_table.add(prevcode_entry.add(k)) + # set PREVCODE = CODE + prevcode = code + + # Detect when we should increase current code size and increase it. + var new_code_size_candidate: int = get_bits_number_for(code_table.counter) + if new_code_size_candidate > current_code_size: + current_code_size = new_code_size_candidate + + return index_stream diff --git a/addons/twitcher/media/native/gif-lzw/lzw.gd.uid b/addons/twitcher/media/native/gif-lzw/lzw.gd.uid new file mode 100644 index 00000000..23fae7b4 --- /dev/null +++ b/addons/twitcher/media/native/gif-lzw/lzw.gd.uid @@ -0,0 +1 @@ +uid://c2gildpalbfq1 diff --git a/addons/twitcher/media/native_image_transformer.gd b/addons/twitcher/media/native_image_transformer.gd new file mode 100644 index 00000000..ac86857a --- /dev/null +++ b/addons/twitcher/media/native_image_transformer.gd @@ -0,0 +1,28 @@ +@icon("res://addons/twitcher/assets/media-loader-icon.svg") +@tool +extends TwitchImageTransformer + +## Native GIF parser written in GDScript and ported to Godot 4. Most of the time stable but there +## are GIF's that may not work cause the file didn't follow the GIF specification. +class_name NativeImageTransformer + + +func is_supporting_animation() -> bool: + return true + + +func convert_image(path: String, buffer_in: PackedByteArray, output_path: String) -> SpriteFrames: + var reader = GifReader.new() + var tex: SpriteFrames; + if buffer_in.size() == 0: + tex = reader.read(path); + else: + tex = reader.load_gif(buffer_in) + _save_converted_file(tex, output_path); + return tex + + +func _save_converted_file(tex: SpriteFrames, output: String): + if not output.is_empty() and tex: + ResourceSaver.save(tex, output, ResourceSaver.SaverFlags.FLAG_COMPRESS); + tex.take_over_path(output); diff --git a/addons/twitcher/media/native_image_transformer.gd.uid b/addons/twitcher/media/native_image_transformer.gd.uid new file mode 100644 index 00000000..8808e01b --- /dev/null +++ b/addons/twitcher/media/native_image_transformer.gd.uid @@ -0,0 +1 @@ +uid://ddwa0dm0qhc3s diff --git a/addons/twitcher/media/twitch_badge_definition.gd b/addons/twitcher/media/twitch_badge_definition.gd new file mode 100644 index 00000000..7c4a42a6 --- /dev/null +++ b/addons/twitcher/media/twitch_badge_definition.gd @@ -0,0 +1,40 @@ +extends RefCounted + +## Definition to load or specify one specific badge +class_name TwitchBadgeDefinition + +const SCALE_1: int = 1 +const SCALE_2: int = 2 +const SCALE_4: int = 4 + +var badge_set: String +var badge_id: String +var scale: int +var channel: String + +var _cache_id: String # This is maybe a bad idea, but solves the issue when the badge won't get found in cache and then it changes the channel to global during loading and so also the cache id + +func scale_1() -> TwitchBadgeDefinition: scale = SCALE_1; return self; +func scale_2() -> TwitchBadgeDefinition: scale = SCALE_2; return self; +func scale_4() -> TwitchBadgeDefinition: scale = SCALE_4; return self; + + +func _init(set_id: String, id: String, badge_scale: int, badge_channel: String) -> void: + badge_set = set_id + badge_id = id + assert(badge_scale == SCALE_1 || badge_scale == SCALE_2 || badge_scale == SCALE_4) + scale = badge_scale + channel = badge_channel + _cache_id = "_".join([ + channel, + badge_set, + badge_id, + scale + ]) + + +func _to_string() -> String: + return "Badge[%s/%s/%s]" % [channel, badge_set, badge_id] + +func get_cache_id() -> String: + return _cache_id diff --git a/addons/twitcher/media/twitch_badge_definition.gd.uid b/addons/twitcher/media/twitch_badge_definition.gd.uid new file mode 100644 index 00000000..d194d68f --- /dev/null +++ b/addons/twitcher/media/twitch_badge_definition.gd.uid @@ -0,0 +1 @@ +uid://dwx6jbouei2tf diff --git a/addons/twitcher/media/twitch_cheermote_definition.gd b/addons/twitcher/media/twitch_cheermote_definition.gd new file mode 100644 index 00000000..4b86314f --- /dev/null +++ b/addons/twitcher/media/twitch_cheermote_definition.gd @@ -0,0 +1,50 @@ +extends RefCounted + +## Definition of a specific cheermote +class_name TwitchCheermoteDefinition + +const THEME_DARK: StringName = &"dark" +const THEME_LIGHT: StringName = &"light" + +const TYPE_ANIMATED: StringName = &"animated_format" +const TYPE_STATIC: StringName = &"static_format" + +const SCALE_1: StringName = &"1" +const SCALE_2: StringName = &"2" +const SCALE_3: StringName = &"3" +const SCALE_4: StringName = &"4" +const SCALE_1_5: StringName = &"1.5" + +const SCALE_MAP: Dictionary[float, StringName] = { + 1: SCALE_1, 2: SCALE_2, 3: SCALE_3, 4: SCALE_4, 1.5: SCALE_1_5 +} + +var prefix: String +var tier: StringName +var theme: StringName = THEME_DARK +var type: StringName = TYPE_ANIMATED +var scale: StringName = SCALE_1 + + +func _init(pre: String, tir: String) -> void: + prefix = pre + tier = tir + +func theme_dark() -> TwitchCheermoteDefinition: theme = THEME_DARK; return self; +func theme_light() -> TwitchCheermoteDefinition: theme = THEME_LIGHT; return self; + +func type_animated() -> TwitchCheermoteDefinition: type = TYPE_ANIMATED; return self; +func type_static() -> TwitchCheermoteDefinition: type = TYPE_STATIC; return self; + +func scale_1() -> TwitchCheermoteDefinition: scale = SCALE_1; return self; +func scale_2() -> TwitchCheermoteDefinition: scale = SCALE_2; return self; +func scale_3() -> TwitchCheermoteDefinition: scale = SCALE_3; return self; +func scale_4() -> TwitchCheermoteDefinition: scale = SCALE_4; return self; +func scale_1_5() -> TwitchCheermoteDefinition: scale = SCALE_1_5; return self; + + +func _to_string() -> String: + return "Cheer[%s/%s]" % [prefix, tier] + +func get_id() -> String: + return "/" + "/".join([ prefix, tier, theme, type, scale ]) diff --git a/addons/twitcher/media/twitch_cheermote_definition.gd.uid b/addons/twitcher/media/twitch_cheermote_definition.gd.uid new file mode 100644 index 00000000..7359c6f5 --- /dev/null +++ b/addons/twitcher/media/twitch_cheermote_definition.gd.uid @@ -0,0 +1 @@ +uid://cntpsdb4qwtex diff --git a/addons/twitcher/media/twitch_emote_definition.gd b/addons/twitcher/media/twitch_emote_definition.gd new file mode 100644 index 00000000..739f9920 --- /dev/null +++ b/addons/twitcher/media/twitch_emote_definition.gd @@ -0,0 +1,42 @@ +extends RefCounted + +## Used to define what emotes to load to be typesafe and don't request invalid data. +class_name TwitchEmoteDefinition + +var id: String +var scale: int +var type: StringName +var theme: StringName + +const SCALE_1: int = 1 +const SCALE_2: int = 2 +const SCALE_3: int = 3 + +const TYPE_DEFAULT: StringName = &"default" +const TYPE_STATIC: StringName = &"static" +const TYPE_ANIMATED: StringName = &"animated" + +const THEME_DARK: StringName = &"dark" +const THEME_LIGHT: StringName = &"light" + +func _init(emote_id: String) -> void: + id = emote_id + scale_1().type_default().theme_dark() + +func scale_1() -> TwitchEmoteDefinition: scale = SCALE_1; return self; +func scale_2() -> TwitchEmoteDefinition: scale = SCALE_2; return self; +func scale_3() -> TwitchEmoteDefinition: scale = SCALE_3; return self; + +func type_default() -> TwitchEmoteDefinition: type = TYPE_DEFAULT; return self; +func type_static() -> TwitchEmoteDefinition: type = TYPE_STATIC; return self; +func type_animated() -> TwitchEmoteDefinition: type = TYPE_ANIMATED; return self; + +func theme_dark() -> TwitchEmoteDefinition: theme = THEME_DARK; return self; +func theme_light() -> TwitchEmoteDefinition: theme = THEME_LIGHT; return self; + +func _to_string() -> String: + return "Emote[%s]" % id + +## Returns its unique filename +func get_file_name() -> String: + return "%s_%s_%s_%s" % [scale, type, theme, id] diff --git a/addons/twitcher/media/twitch_emote_definition.gd.uid b/addons/twitcher/media/twitch_emote_definition.gd.uid new file mode 100644 index 00000000..3022fe6b --- /dev/null +++ b/addons/twitcher/media/twitch_emote_definition.gd.uid @@ -0,0 +1 @@ +uid://dhyboroqtixko diff --git a/addons/twitcher/media/twitch_image_transformer.gd b/addons/twitcher/media/twitch_image_transformer.gd new file mode 100644 index 00000000..946a72d8 --- /dev/null +++ b/addons/twitcher/media/twitch_image_transformer.gd @@ -0,0 +1,39 @@ +@icon("res://addons/twitcher/assets/media-loader-icon.svg") +@tool +extends Resource + +## Most simple image transformer that doesn't support GIF's uses builtin functionalities of godot. +class_name TwitchImageTransformer + +static var _log: TwitchLogger = TwitchLogger.new("TwitchImageTransformer") + +## Used when the image can't be transformed +@export var fallback_texture: Texture2D = preload("res://addons/twitcher/assets/fallback_texture.tres") + + +func is_supporting_animation() -> bool: + return false + + +func is_supported() -> bool: + return true + + +func convert_image(path: String, buffer_in: PackedByteArray, output_path: String) -> SpriteFrames: + if ResourceLoader.has_cached(output_path): + return ResourceLoader.load(output_path) + var img := Image.new() + var err = img.load_png_from_buffer(buffer_in) + var sprite_frames = SpriteFrames.new() + var texture : Texture + if err == OK: + texture = ImageTexture.new() + texture.set_image(img) + sprite_frames.add_frame(&"default", texture) + ResourceSaver.save(sprite_frames, output_path, ResourceSaver.SaverFlags.FLAG_COMPRESS) + sprite_frames.take_over_path(path) + else: + sprite_frames.add_frame(&"default", fallback_texture) + _log.e("Can't load %s use fallback" % output_path) + + return sprite_frames diff --git a/addons/twitcher/media/twitch_image_transformer.gd.uid b/addons/twitcher/media/twitch_image_transformer.gd.uid new file mode 100644 index 00000000..32c0232e --- /dev/null +++ b/addons/twitcher/media/twitch_image_transformer.gd.uid @@ -0,0 +1 @@ +uid://6v8jnfjwbnhm diff --git a/addons/twitcher/media/twitch_media_loader.gd b/addons/twitcher/media/twitch_media_loader.gd new file mode 100644 index 00000000..d20c3ed8 --- /dev/null +++ b/addons/twitcher/media/twitch_media_loader.gd @@ -0,0 +1,450 @@ +@icon("res://addons/twitcher/assets/media-loader-icon.svg") +@tool +extends Twitcher + +## Will load badges, icons and profile images +class_name TwitchMediaLoader + +static var _log: TwitchLogger = TwitchLogger.new("TwitchMediaLoader") + +static var instance: TwitchMediaLoader + +## Called when an emoji was succesfully loaded +signal emoji_loaded(definition: TwitchEmoteDefinition) + +const FALLBACK_TEXTURE = preload("res://addons/twitcher/assets/fallback_texture.tres") +const FALLBACK_PROFILE = preload("res://addons/twitcher/assets/no_profile.png") + +@export var api: TwitchAPI +@export var image_transformer: TwitchImageTransformer = TwitchImageTransformer.new(): + set(val): + image_transformer = val + update_configuration_warnings() +@export var fallback_texture: Texture2D = FALLBACK_TEXTURE +@export var fallback_profile: Texture2D = FALLBACK_PROFILE +@export var image_cdn_host: String = "https://static-cdn.jtvnw.net" +## Will preload the whole badge and emote cache also to editor time (use it when you make a Editor Plugin with Twitch Support) +@export var load_cache_in_editor: bool + +@export_global_dir var cache_emote: String = "user://emotes" +@export_global_dir var cache_badge: String = "user://badges" +@export_global_dir var cache_cheermote: String = "user://cheermote" + +## All requests that are currently in progress +var _requests_in_progress : Array[StringName] +## Badge definition for global and the channel. +var _cached_badges : Dictionary = {} +## Emote definition for global and the channel. +var _cached_emotes : Dictionary = {} +## Key: String Cheer Key(Prefix) | Value: TwitchCheermote +var _cached_cheermotes: Dictionary[String, TwitchCheermote] = {} + +## All cached emotes, badges, cheermotes +## Is needed that the garbage collector isn't deleting our cache. +var _cached_images : Array[SpriteFrames] = [] +var _host_parser: RegEx = RegEx.create_from_string("(https://.*?)/") +var static_image_transformer = TwitchImageTransformer.new() +var _client: BufferedHTTPClient + + +func _ready() -> void: + _client = BufferedHTTPClient.new() + _client.name = "TwitchMediaLoaderClient" + add_child(_client) + _load_cache() + if api == null: api = TwitchAPI.instance + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +## Loading all images from the directory into the memory cache +func _load_cache() -> void: + if Engine.is_editor_hint() || load_cache_in_editor: + _cache_directory(cache_emote) + _cache_directory(cache_badge) + + +func _cache_directory(path: String): + DirAccess.make_dir_recursive_absolute(path) + var files: PackedStringArray = DirAccess.get_files_at(path) + for file in files: + if file.ends_with(".res"): + var res_path: String = path.path_join(file) + var sprite_frames: SpriteFrames = ResourceLoader.load(res_path, "SpriteFrames") + var spriteframe_path: String = res_path.trim_suffix(".res") + sprite_frames.take_over_path(spriteframe_path) + _cached_images.append(sprite_frames) + +#region Emotes + +func preload_emotes(channel_id: String = "global") -> void: + + if (!_cached_emotes.has(channel_id)): + var response + if channel_id == "global": + _log.i("Preload global emotes") + response = await api.get_global_emotes() + else: + _log.i("Preload channel(%s) emotes" % channel_id) + response = await api.get_channel_emotes(channel_id) + _cached_emotes[channel_id] = _map_emotes(response) + + +## Returns requested emotes. +## Key: EmoteID as String | Value: SpriteFrames +func get_emotes(emote_ids : Array[String]) -> Dictionary[String, SpriteFrames]: + _log.i("Get emotes: %s" % emote_ids) + var requests: Array[TwitchEmoteDefinition] = [] + for id: String in emote_ids: + requests.append(TwitchEmoteDefinition.new(id)) + var emotes: Dictionary[TwitchEmoteDefinition, SpriteFrames] = await get_emotes_by_definition(requests) + var result: Dictionary[String, SpriteFrames] = {} + # Remap the emotes to string value easier for processing + for requested_emote: TwitchEmoteDefinition in requests: + result[requested_emote.id] = emotes[requested_emote] + return result + + +## Returns requested emotes. +## Key: TwitchEmoteDefinition | Value: SpriteFrames +func get_emotes_by_definition(emote_definitions : Array[TwitchEmoteDefinition]) -> Dictionary[TwitchEmoteDefinition, SpriteFrames]: + var response: Dictionary[TwitchEmoteDefinition, SpriteFrames] = {} + var requests: Dictionary[TwitchEmoteDefinition, BufferedHTTPClient.RequestData] = {} + + for emote_definition: TwitchEmoteDefinition in emote_definitions: + var original_file_cache_path: String = _get_emote_cache_path(emote_definition) + var spriteframe_path: String = _get_emote_cache_path_spriteframe(emote_definition) + if ResourceLoader.has_cached(spriteframe_path): + _log.d("Use cached emote %s" % emote_definition) + response[emote_definition] = ResourceLoader.load(spriteframe_path) + continue + + if not image_transformer.is_supporting_animation(): + emote_definition.type_static() + + if _requests_in_progress.has(original_file_cache_path): continue + _requests_in_progress.append(original_file_cache_path) + _log.d("Request emote %s" % emote_definition) + var request : BufferedHTTPClient.RequestData = _load_emote(emote_definition) + requests[emote_definition] = request + + for emote_definition : TwitchEmoteDefinition in requests: + var original_file_cache_path : String = _get_emote_cache_path(emote_definition) + var spriteframe_path : String = _get_emote_cache_path_spriteframe(emote_definition) + var request : BufferedHTTPClient.RequestData = requests[emote_definition] + var sprite_frames : SpriteFrames = await _convert_response(request, original_file_cache_path, spriteframe_path) + response[emote_definition] = sprite_frames + _cached_images.append(sprite_frames) + _requests_in_progress.erase(original_file_cache_path) + emoji_loaded.emit(emote_definition) + + for emote_definition: TwitchEmoteDefinition in emote_definitions: + if not response.has(emote_definition): + var cache : String = _get_emote_cache_path_spriteframe(emote_definition) + response[emote_definition] = ResourceLoader.load(cache) + + return response + + +## Returns the path where the raw emoji should be cached +func _get_emote_cache_path(emote_definition: TwitchEmoteDefinition) -> String: + var file_name : String = emote_definition.get_file_name() + return cache_emote.path_join(file_name) + + +## Returns the path where the converted spriteframe should be cached +func _get_emote_cache_path_spriteframe(emote_definition: TwitchEmoteDefinition) -> String: + var file_name : String = emote_definition.get_file_name() + ".res" + return cache_emote.path_join(file_name) + + +func _load_emote(emote_definition : TwitchEmoteDefinition) -> BufferedHTTPClient.RequestData: + var request_path : String = "/emoticons/v2/%s/%s/%s/%1.1f" % [emote_definition.id, emote_definition.type, emote_definition.theme, emote_definition.scale] + return _client.request(image_cdn_host + request_path, HTTPClient.METHOD_GET, {}, "") + + +func _map_emotes(result: Variant) -> Dictionary: + var mappings : Dictionary = {} + var emotes : Array = result.get("data") + if emotes == null: + return mappings + for emote in emotes: + mappings[emote.get("id")] = emote + return mappings + + +func get_cached_emotes(channel_id) -> Dictionary: + if not _cached_emotes.has(channel_id): + await preload_emotes(channel_id) + return _cached_emotes[channel_id] + +#endregion + +#region Badges + +func preload_badges(channel_id: String = "global") -> void: + if not _cached_badges.has(channel_id): + var response: Variant # TwitchGetGlobalChatBadges.Response | TwitchGetChannelChatBadges.Response + if channel_id == "global": + _log.i("Preload global badges") + response = await(api.get_global_chat_badges()) + else: + _log.i("Preload channel(%s) badges" % channel_id) + response = await(api.get_channel_chat_badges(channel_id)) + _cached_badges[channel_id] = _cache_badges(response) + + +## Returns the requested badge either from cache or loads from web. Scale can be 1, 2 or 4. +## Key: TwitchBadgeDefinition | Value: SpriteFrames +func get_badges(badges: Array[TwitchBadgeDefinition]) -> Dictionary[TwitchBadgeDefinition, SpriteFrames]: + var response: Dictionary[TwitchBadgeDefinition, SpriteFrames] = {} + var requests: Dictionary[TwitchBadgeDefinition, BufferedHTTPClient.RequestData] = {} + + for badge_definition : TwitchBadgeDefinition in badges: + var cache_id : String = badge_definition.get_cache_id() + var badge_path : String = cache_badge.path_join(cache_id) + if ResourceLoader.has_cached(badge_path): + _log.d("Use cached badge %s" % badge_definition) + response[badge_definition] = ResourceLoader.load(badge_path) + else: + _log.d("Request badge %s" % badge_definition) + var request : BufferedHTTPClient.RequestData = await _load_badge(badge_definition) + requests[badge_definition] = request + + for badge_definition : TwitchBadgeDefinition in requests: + var request = requests[badge_definition] + var id : String = badge_definition.get_cache_id() + var cache_path : String = cache_badge.path_join(id) + var spriteframe_path : String = cache_badge.path_join(id) + ".res" + var sprite_frames : SpriteFrames = await _convert_response(request, cache_path, spriteframe_path) + response[badge_definition] = sprite_frames + _cached_images.append(sprite_frames) + + return response + + +func _load_badge(badge_definition: TwitchBadgeDefinition) -> BufferedHTTPClient.RequestData: + var channel_id : String = badge_definition.channel + var badge_set : String = badge_definition.badge_set + var badge_id : String = badge_definition.badge_id + var scale : int = badge_definition.scale + + var is_global_chanel : bool = channel_id == "global" + if not _cached_badges.has(channel_id): + await preload_badges(channel_id) + var channel_has_badge : bool = _cached_badges[channel_id].has(badge_set) && _cached_badges[channel_id][badge_set]["versions"].has(badge_id) + if not is_global_chanel and not channel_has_badge: + badge_definition.channel = "global" + return await _load_badge(badge_definition) + + var request_path : String = _cached_badges[channel_id][badge_set]["versions"][badge_id]["image_url_%sx" % scale] + return _client.request(request_path, HTTPClient.METHOD_GET, {}, "") + + +## Maps the badges into a dict of category / versions / badge_id +func _cache_badges(result: Variant) -> Dictionary: + var mappings : Dictionary = {} + var badges : Array = result["data"] + for badge in badges: + if not mappings.has(badge["set_id"]): + mappings[badge["set_id"]] = { + "set_id": badge["set_id"], + "versions" : {} + } + for version in badge["versions"]: + mappings[badge["set_id"]]["versions"][version["id"]] = version + return mappings + + +func get_cached_badges(channel_id: String) -> Dictionary: + if(!_cached_badges.has(channel_id)): + await preload_badges(channel_id) + return _cached_badges[channel_id] +#endregion + +#region Cheermote + +class CheerResult extends RefCounted: + var cheermote: TwitchCheermote + var tier: TwitchCheermote.Tiers + var spriteframes: SpriteFrames + func _init(cheer: TwitchCheermote, t: TwitchCheermote.Tiers, sprites: SpriteFrames): + cheermote = cheer + tier = t + spriteframes = sprites + + +func preload_cheemote() -> void: + if not _cached_cheermotes.is_empty(): return + _log.i("Preload cheermotes") + var cheermote_response: TwitchGetCheermotes.Response = await api.get_cheermotes(null) + for cheermote: TwitchCheermote in cheermote_response.data: + _log.d("- found %s" % cheermote.prefix) + var key: String = _get_cheermote_key(cheermote) + _cached_cheermotes[key] = cheermote + + +func all_cheermotes() -> Array[TwitchCheermote]: + var cheermotes: Array[TwitchCheermote] = [] + cheermotes.assign(_cached_cheermotes.values()) + return cheermotes + + +## Resolves a info with spriteframes for a specific cheer definition contains also spriteframes for the given tier. +## Can be null when not found. +func get_cheer_info(cheermote_definition: TwitchCheermoteDefinition) -> CheerResult: + await preload_cheemote() + var key: String = _get_cheermote_key(cheermote_definition) + var cheermote : TwitchCheermote = _cached_cheermotes[key] + for cheertier : TwitchCheermote.Tiers in cheermote.tiers: + if cheertier.id == cheermote_definition.tier: + var sprite_frames: SpriteFrames = await _get_cheermote_sprite_frames(cheertier, cheermote_definition) + return CheerResult.new(cheermote, cheertier, sprite_frames) + return null + + +## Finds the tier depending on the given number +func find_cheer_tier(number: int, cheer_data: TwitchCheermote) -> TwitchCheermote.Tiers: + var current_tier : TwitchCheermote.Tiers = cheer_data.tiers[0] + for tier : TwitchCheermote.Tiers in cheer_data.tiers: + if tier.min_bits < number && current_tier.min_bits < tier.min_bits: + current_tier = tier + return current_tier + + +## Returns spriteframes mapped by tier for a cheermote +## Key: TwitchCheermote.Tiers | Value: SpriteFrames +func get_cheermotes(cheermote_definition: TwitchCheermoteDefinition) -> Dictionary[TwitchCheermote.Tiers, SpriteFrames]: + await preload_cheemote() + var response : Dictionary[TwitchCheermote.Tiers, SpriteFrames] = {} + var requests : Dictionary[TwitchCheermote.Tiers, BufferedHTTPClient.RequestData] = {} + var key : String = _get_cheermote_key(cheermote_definition) + var cheer : TwitchCheermote = _cached_cheermotes[key] + for tier : TwitchCheermote.Tiers in cheer.tiers: + var id = cheermote_definition.get_id() + if ResourceLoader.has_cached(id): + _log.d("Use cached cheer %s" % cheermote_definition) + response[tier] = ResourceLoader.load(id) + if not image_transformer.is_supporting_animation(): + cheermote_definition.type_static() + else: + _log.d("Request cheer %s" % cheermote_definition) + requests[tier] = _request_cheermote(tier, cheermote_definition) + + for tier: TwitchCheermote.Tiers in requests: + var id = cheermote_definition.get_id() + var request = requests[tier] + var sprite_frames = await _wait_for_cheeremote(request, id) + response[tier] = sprite_frames + return response + + +func _get_cheermote_sprite_frames(tier: TwitchCheermote.Tiers, cheermote_definition: TwitchCheermoteDefinition) -> SpriteFrames: + var id = cheermote_definition.get_id() + if ResourceLoader.has_cached(id): + return ResourceLoader.load(id) + else: + var request : BufferedHTTPClient.RequestData = _request_cheermote(tier, cheermote_definition) + if request == null: + var frames : SpriteFrames = SpriteFrames.new() + frames.add_frame("default", fallback_texture) + return frames + return await _wait_for_cheeremote(request, id) + + +func _wait_for_cheeremote(request: BufferedHTTPClient.RequestData, cheer_id: String) -> SpriteFrames: + var response : BufferedHTTPClient.ResponseData = await _client.wait_for_request(request) + var cache_path : String = cache_cheermote.path_join(cheer_id) + var sprite_frames : SpriteFrames = await image_transformer.convert_image( + cache_path, + response.response_data, + cache_path + ".res") as SpriteFrames + sprite_frames.take_over_path(cheer_id) + _cached_images.append(sprite_frames) + return sprite_frames + + +func _request_cheermote(cheer_tier: TwitchCheermote.Tiers, cheermote: TwitchCheermoteDefinition) -> BufferedHTTPClient.RequestData: + var img_path : String = cheer_tier.images[cheermote.theme][cheermote.type][cheermote.scale] + var host_result : RegExMatch = _host_parser.search(img_path) + if host_result == null: return null + var host : String = host_result.get_string(1) + return _client.request(img_path, HTTPClient.METHOD_GET, {}, "") + + +## Get's the caching key from cheermote (TwitchCheermote or TwitchCheermoteDefinition) +func _get_cheermote_key(cheermote: Variant) -> String: + if "prefix" in cheermote: + return cheermote.prefix.to_lower() + return "%s" % cheermote + +#endregion + +#region Utilities + +func _get_configuration_warnings() -> PackedStringArray: + if image_transformer == null || not image_transformer.is_supported(): + return ["Image transformer is misconfigured"] + return [] + + +func load_image(url: String) -> Image: + var request : BufferedHTTPClient.RequestData = _client.request(url, HTTPClient.METHOD_GET, {}, "") + var response : BufferedHTTPClient.ResponseData = await _client.wait_for_request(request) + var temp_file : FileAccess = FileAccess.create_temp(FileAccess.ModeFlags.WRITE_READ, "image_", url.get_extension(), true) + temp_file.store_buffer(response.response_data) + temp_file.flush() + var image : Image = Image.load_from_file(temp_file.get_path()) + return image + + +## Get the image of an user +func load_profile_image(user: TwitchUser) -> ImageTexture: + if user == null: return fallback_profile + if ResourceLoader.has_cached(user.profile_image_url): + return ResourceLoader.load(user.profile_image_url) + var request := _client.request(user.profile_image_url, HTTPClient.METHOD_GET, {}, "") + var response_data := await _client.wait_for_request(request) + var texture : ImageTexture = ImageTexture.new() + var response := response_data.response_data + if not response.is_empty(): + var img := Image.new() + var content_type: String = response_data.response_header["Content-Type"] + var error: Error + if content_type.containsn("image/png"): + error = img.load_png_from_buffer(response) + elif content_type.containsn("image/jpeg"): + error = img.load_jpg_from_buffer(response) + else: + push_error("Unknown content type: \"%s\"" % content_type) + return fallback_profile + if error != OK: + push_error("Can't load profile picture of %s cause of %s" % [user.display_name, error_string(error)]) + return fallback_profile + texture.set_image(img) + else: + # Don't use `texture = fallback_profile` as texture cause the path will be taken over + # for caching purpose! + texture.set_image(fallback_profile.get_image()) + texture.take_over_path(user.profile_image_url) + return texture + + +const GIF_HEADER: PackedByteArray = [71, 73, 70] +func _convert_response(request: BufferedHTTPClient.RequestData, cache_path: String, spriteframe_path: String) -> SpriteFrames: + var response = await _client.wait_for_request(request) + var response_data = response.response_data as PackedByteArray + var file_head = response_data.slice(0, 3) + # REMARK: don't use content-type... twitch doesn't check and sends PNGs with GIF content type. + if file_head == GIF_HEADER: + return await image_transformer.convert_image(cache_path, response_data, spriteframe_path) as SpriteFrames + else: + return await static_image_transformer.convert_image(cache_path, response_data, spriteframe_path) as SpriteFrames + +#endregion diff --git a/addons/twitcher/media/twitch_media_loader.gd.uid b/addons/twitcher/media/twitch_media_loader.gd.uid new file mode 100644 index 00000000..615049f9 --- /dev/null +++ b/addons/twitcher/media/twitch_media_loader.gd.uid @@ -0,0 +1 @@ +uid://d4lyup0vy1wtu diff --git a/addons/twitcher/plugin.cfg b/addons/twitcher/plugin.cfg new file mode 100644 index 00000000..e44f9d0d --- /dev/null +++ b/addons/twitcher/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Twitcher" +description="A plugin to use Twitch API in Godot." +author="kani_dev" +version="2.2.2" +script="plugin.gd" diff --git a/addons/twitcher/plugin.gd b/addons/twitcher/plugin.gd new file mode 100644 index 00000000..4fb1dca5 --- /dev/null +++ b/addons/twitcher/plugin.gd @@ -0,0 +1,102 @@ +@tool +extends EditorPlugin + +static var _log : TwitchLogger = TwitchLogger.new("Twitcher Plugin") + +const REGENERATE_API_LABEL: String = "Regenerate Twitch Api" +const OPEN_SETUP_LABEL: String = "Twitcher Setup" + +# oOuch imports +const OauthSettingInspector = preload("res://addons/twitcher/lib/oOuch/oauth_setting_inspector.gd") +const TokenInspector = preload("res://addons/twitcher/lib/oOuch/oauth_token_inspector.gd") + +# Twitcher imports +const TwitchScopeInspectorPlugin = preload("res://addons/twitcher/editor/inspector/twitch_scope_inspector.gd") +const TwitchEventsubInspectorPlugin = preload("res://addons/twitcher/editor/inspector/twitch_eventsub_inspector.gd") +const TwitchEventsubConfigInspectorPlugin = preload("res://addons/twitcher/editor/inspector/twitch_eventsub_config_inspector.gd") +const TwitchMediaLoaderInspector = preload("res://addons/twitcher/editor/inspector/twitch_media_loader_inspector.gd") +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +const TwitchUserInspector = preload("res://addons/twitcher/editor/inspector/twitch_user_inspector.gd") +const TwitchRewardInspector = preload("res://addons/twitcher/editor/inspector/twitch_reward_inspector.gd") + +var generator: TwitchAPIGenerator +var parser: TwitchAPIParser + +var gif_importer_imagemagick: GifImporterImagemagick = GifImporterImagemagick.new() +var gif_importer_native: GifImporterNative = GifImporterNative.new() +var eventsub_config_inspector: TwitchEventsubConfigInspectorPlugin = TwitchEventsubConfigInspectorPlugin.new() +var eventsub_inspector: TwitchEventsubInspectorPlugin = TwitchEventsubInspectorPlugin.new() +var scope_inspector: TwitchScopeInspectorPlugin = TwitchScopeInspectorPlugin.new() +var oauth_setting_inspector: OauthSettingInspector = OauthSettingInspector.new() +var token_inspector: TokenInspector = TokenInspector.new() +var media_loader_inspector: TwitchMediaLoaderInspector = TwitchMediaLoaderInspector.new() +var user_inspector: TwitchUserInspector = TwitchUserInspector.new() +var reward_inspector: TwitchRewardInspector = TwitchRewardInspector.new() +var settings: TwitchEditorSettings = TwitchEditorSettings.new() +var current_setup_window: Node + +func _enter_tree(): + _log.i("Start Twitcher loading...") + TwitchEditorSettings.setup() + + token_inspector.token_info_scene = preload("res://addons/twitcher/editor/inspector/twitch_token_info.tscn") + + add_tool_menu_item(REGENERATE_API_LABEL, generate_api) + add_tool_menu_item(OPEN_SETUP_LABEL, open_setup) + + add_inspector_plugin(eventsub_config_inspector) + add_inspector_plugin(eventsub_inspector) + add_inspector_plugin(scope_inspector) + add_inspector_plugin(oauth_setting_inspector) + add_inspector_plugin(token_inspector) + add_inspector_plugin(media_loader_inspector) + add_inspector_plugin(user_inspector) + add_inspector_plugin(reward_inspector) + add_import_plugin(gif_importer_native) + if is_magick_available(): + add_import_plugin(gif_importer_imagemagick) + + if TwitchEditorSettings.show_setup_on_startup: open_setup() + _log.i("Twitcher loading ended") + + +func _exit_tree(): + remove_import_plugin(gif_importer_native) + if is_magick_available(): + remove_import_plugin(gif_importer_imagemagick) + + remove_inspector_plugin(eventsub_config_inspector) + remove_inspector_plugin(eventsub_inspector) + remove_inspector_plugin(scope_inspector) + remove_inspector_plugin(oauth_setting_inspector) + remove_inspector_plugin(token_inspector) + remove_inspector_plugin(media_loader_inspector) + remove_inspector_plugin(user_inspector) + remove_inspector_plugin(reward_inspector) + if Engine.is_editor_hint(): + remove_tool_menu_item(REGENERATE_API_LABEL) + + _log.i("Twitcher Unloaded") + + +func open_setup() -> void: + if is_instance_valid(current_setup_window): return + + current_setup_window = load("res://addons/twitcher/editor/setup/setup.tscn").instantiate() + add_child(current_setup_window) + + +func generate_api() -> void: + generator = TwitchAPIGenerator.new() + parser = TwitchAPIParser.new() + generator.parser = parser + add_child(generator) + add_child(parser) + await parser.parse_api() + generator.generate_api() + remove_child(generator) + remove_child(parser) + +func is_magick_available() -> bool: + var transformer = MagicImageTransformer.new() + return transformer.is_supported() diff --git a/addons/twitcher/plugin.gd.uid b/addons/twitcher/plugin.gd.uid new file mode 100644 index 00000000..5a59c64e --- /dev/null +++ b/addons/twitcher/plugin.gd.uid @@ -0,0 +1 @@ +uid://bm71dx6iwl1nh diff --git a/addons/twitcher/reward/twitch_redeem_listener.gd b/addons/twitcher/reward/twitch_redeem_listener.gd new file mode 100644 index 00000000..7d0133b3 --- /dev/null +++ b/addons/twitcher/reward/twitch_redeem_listener.gd @@ -0,0 +1,123 @@ +@tool +@icon("res://addons/twitcher/assets/redemption-icon.svg") +extends Twitcher + +## Helps to listen for redeems of the viewer +class_name TwitchRedeemListener + +static var _log: TwitchLogger = TwitchLogger.new("TwitchRedeemListener") + +static var _open_tracked_redemptions: Dictionary[String, TwitchRedemption] = {} + +@export var rewards_to_listen: Array[TwitchReward] = [] +@export var eventsub: TwitchEventsub +@export var api: TwitchAPI + +## Should the node automatically subscribe to the needed eventsubs in the ready function. +@export var ensure_subscriptions_on_ready: bool = true + +## Called when one of the rewards that this node is listenting is getting redeemed +signal redeemed(redemption: TwitchRedemption) + + +func _ready() -> void: + if eventsub == null: eventsub = TwitchEventsub.instance + if api == null: api = TwitchAPI.instance + + eventsub.event_received.connect(_on_event) + if ensure_subscriptions_on_ready: ensure_subscriptions() + + +func ensure_subscriptions() -> void: + var add_subscriptions: Array[TwitchEventsubConfig] = eventsub.get_subscription_by_type( + TwitchEventsubDefinition.Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD) + var update_subscriptions: Array[TwitchEventsubConfig] = eventsub.get_subscription_by_type( + TwitchEventsubDefinition.Type.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE) + var broadcaster: TwitchUser = await TwitchService.get_current_user_via_api(api) + if add_subscriptions.is_empty(): + var config: TwitchEventsubConfig = TwitchEventsubConfig.create(TwitchEventsubDefinition.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, { + "broadcaster_user_id": broadcaster.id + }) + eventsub.subscribe(config) + if update_subscriptions.is_empty(): + var config: TwitchEventsubConfig = TwitchEventsubConfig.create(TwitchEventsubDefinition.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, { + "broadcaster_user_id": broadcaster.id + }) + eventsub.subscribe(config) + + +func _on_event(event: TwitchEventsub.Event) -> void: + if event.type == TwitchEventsubDefinition.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD: + var redemption_event: TwitchChannelPointsCustomRewardRedemptionAddEvent = TwitchChannelPointsCustomRewardRedemptionAddEvent.from_json(event.data) + _add_redemption_event(redemption_event) + elif event.type == TwitchEventsubDefinition.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE: + var redemption_event: TwitchChannelPointsCustomRewardRedemptionUpdateEvent = TwitchChannelPointsCustomRewardRedemptionUpdateEvent.from_json(event.data) + if _open_tracked_redemptions.has(redemption_event.id): + _update_redemption_event(redemption_event) + + +func _find_by_id(reward: TwitchReward, reward_id_to_search: String) -> bool: + return reward.id == reward_id_to_search + + +func _add_redemption_event(redemption_event: TwitchChannelPointsCustomRewardRedemptionAddEvent) -> void: + var redemption_id: String = redemption_event.id + var reward_id: String = redemption_event.reward.id + var idx: int = rewards_to_listen.find_custom(_find_by_id.bind(reward_id)) + if idx == -1: return + + var reward = rewards_to_listen[idx] + var opt = TwitchGetUsers.Opt.new() + var user_ids: Array[String] = [redemption_event.broadcaster_user_id, redemption_event.user_id] + opt.id = user_ids + var users = await api.get_users(opt) + if users.response.response_code != 200: + _log.e("Couldn't load users for the redemption of %s" % reward.title) + return + var user_idx: int = users.data.find_custom(_by_user_id.bind(redemption_event.user_id)) + var user: TwitchUser = users.data[user_idx] + var broadcaster_idx: int = users.data.find_custom(_by_user_id.bind(redemption_event.broadcaster_user_id)) + var broadcaster: TwitchUser = users.data[broadcaster_idx] + var redemption: TwitchRedemption = TwitchRedemption.new(redemption_id, reward, broadcaster, user) + redemption.redeemed_at = redemption_event.redeemed_at + redemption.status = redemption_event.status + redemption.user_input = redemption_event.user_input + redemption._fullfill_callback = fulfill_redemption + redemption._cancel_callback = cancel_redemption + + _open_tracked_redemptions[redemption_event.id] = redemption + redeemed.emit(redemption) + + +func _update_redemption_event(update_event: TwitchChannelPointsCustomRewardRedemptionUpdateEvent) -> void: + var redemption: TwitchRedemption = _open_tracked_redemptions[update_event.id] + if redemption.status != TwitchRedemption.Status.UNFULFILLED: return + + match update_event.status: + TwitchRedemption.Status.FULFILLED: + redemption.notify_fullfilled() + TwitchRedemption.Status.CANCELED: + redemption.notify_cancelled() + + +## Tries to fullfill the redemption in error case it will return null. +func fulfill_redemption(redemption_id: String, reward: TwitchReward, broadcaster_id: String) -> TwitchCustomRewardRedemption: + return await _update_redemption(true, redemption_id, reward, broadcaster_id) + + +## Tries to cancel the redemption in error case it will return null. +func cancel_redemption(redemption_id: String, reward: TwitchReward, broadcaster_id: String) -> TwitchCustomRewardRedemption: + return await _update_redemption(false, redemption_id, reward, broadcaster_id) + + +func _update_redemption(fullfill: bool, redemption_id: String, reward: TwitchReward, broadcaster_id: String) -> TwitchCustomRewardRedemption: + var body: TwitchUpdateRedemptionStatus.Body = TwitchUpdateRedemptionStatus.Body.new() + body.status = "FULFILLED" if fullfill else "CANCELED" + var response: TwitchUpdateRedemptionStatus.Response = await api.update_redemption_status(body, [redemption_id], reward.id, broadcaster_id) + if response.response.response_code != 200 || response.data.is_empty(): + return null + return response.data[0] + + +func _by_user_id(user: TwitchUser, id: String) -> bool: + return user.id == id diff --git a/addons/twitcher/reward/twitch_redeem_listener.gd.uid b/addons/twitcher/reward/twitch_redeem_listener.gd.uid new file mode 100644 index 00000000..0e9fe7c3 --- /dev/null +++ b/addons/twitcher/reward/twitch_redeem_listener.gd.uid @@ -0,0 +1 @@ +uid://b8b32wrju6f63 diff --git a/addons/twitcher/reward/twitch_redemption.gd b/addons/twitcher/reward/twitch_redemption.gd new file mode 100644 index 00000000..d48c842a --- /dev/null +++ b/addons/twitcher/reward/twitch_redemption.gd @@ -0,0 +1,80 @@ +@tool +extends RefCounted + +## Data class for holding relevant information about a redemption +class_name TwitchRedemption + +enum Status { + UNKNOWN = 0, + UNFULFILLED = 1, + FULFILLED = 2, + CANCELED = 3 +} + +## The unique redemption id +var id: String + +## The reward that was redeemed. +var reward: TwitchReward + +## requested broadcaster ID. +var broadcaster: TwitchUser + +## The user that redeemed the reward. +var user: TwitchUser + +## The user input provided. Empty string if not provided. +var user_input: String + +## Defaults to "unfulfilled". Possible values are "unknown", "unfulfilled", "fulfilled", and "canceled". +var status: Status + +## RFC3339 timestamp of when the reward was redeemed. +var redeemed_at: String + +# I could just give this object the TwitchRedeemListener but this would cause circular reference +# the TwitchRedeemListener creates this objects so this object shouldn't know about it + +var _fullfill_callback: Callable +var _cancel_callback: Callable + +## Send when the redemption was fullfilled either within the app or externally +signal fullfilled + +## Send when the redemption was canceled either within the app or externally +signal cancelled + + +func _init(redemption_id: String, twitch_reward: TwitchReward, twitch_broadcaster: TwitchUser, twitch_user: TwitchUser): + id = redemption_id + reward = twitch_reward + broadcaster = twitch_broadcaster + user = twitch_user + + +## Fullfill the redemption and remove the channel points +func fullfill() -> void: + if not _fullfill_callback: printerr("Can't fullfill without callback") + var success = await _fullfill_callback.call(id, reward, broadcaster.id) + if success != null: notify_fullfilled() + + +## When the redeem got fullfilled +func notify_fullfilled() -> void: + if status != Status.FULFILLED: + status = Status.FULFILLED + fullfilled.emit() + + +## Cancel the redemption and +func cancel() -> void: + if not _cancel_callback: printerr("Can't fullfill without callback") + var success = await _cancel_callback.call(id, reward, broadcaster.id) + if success != null: notify_cancelled() + + +## When the redeem got fullfilled +func notify_cancelled() -> void: + if status != Status.CANCELED: + status = Status.CANCELED + cancelled.emit() diff --git a/addons/twitcher/reward/twitch_redemption.gd.uid b/addons/twitcher/reward/twitch_redemption.gd.uid new file mode 100644 index 00000000..9eaa2108 --- /dev/null +++ b/addons/twitcher/reward/twitch_redemption.gd.uid @@ -0,0 +1 @@ +uid://b1ya6wvfomt6g diff --git a/addons/twitcher/reward/twitch_reward.gd b/addons/twitcher/reward/twitch_reward.gd new file mode 100644 index 00000000..f19e77ae --- /dev/null +++ b/addons/twitcher/reward/twitch_reward.gd @@ -0,0 +1,103 @@ +@tool +@icon("res://addons/twitcher/assets/reward-icon.svg") +extends Resource + +class_name TwitchReward + +## The ID that uniquely identifies this custom reward. +@export var id: String + +## Owner of this reward +@export var broadcaster_user: TwitchUser + +## The title of the reward. +@export var title: String + +## The prompt shown to the viewer when they redeem the reward if user input is required. See the `is_user_input_required` field. +@export var description: String + +## The cost of the reward in Channel Points. +@export var cost: int = 1 + +## Readonly, there is no API on Twitch side to update. 28x28 A custom images for the reward. This field is **null** if the broadcaster didn’t upload images. +@export var image_1: Image + +## Readonly, there is no API on Twitch side to update. 56x56 A custom images for the reward. This field is **null** if the broadcaster didn’t upload images. +@export var image_2: Image + +## Readonly, there is no API on Twitch side to update. 112x112 A custom images for the reward. This field is **null** if the broadcaster didn’t upload images. +@export var image_4: Image + +## Readonly, there is no API on Twitch side to update. 28x28 default images for the reward. +@export var default_image_1: CompressedTexture2D = preload("res://addons/twitcher/assets/default-1.png") + +## Readonly, there is no API on Twitch side to update. 56x56 default images for the reward. +@export var default_image_2: CompressedTexture2D = preload("res://addons/twitcher/assets/default-2.png") + +## Readonly, there is no API on Twitch side to update. 112x112 default images for the reward. +@export var default_image_4: CompressedTexture2D = preload("res://addons/twitcher/assets/default-4.png") + +## The background color to use for the reward. The color is in Hex format (for example, #00E5CB). +@export var background_color: Color + +## A Boolean value that determines whether the reward is enabled. Is **true** if enabled; otherwise, **false**. Disabled rewards aren’t shown to the user. +@export var is_enabled: bool + +## A Boolean value that determines whether the user must enter information when they redeem the reward. Is **true** if the user is prompted. +@export var is_user_input_required: bool + +## A Boolean value that determines whether the reward is currently paused. Is **true** if the reward is paused. Viewers can’t redeem paused rewards. +@export var is_paused: bool + +## A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed. If **false**, status is set to UNFULFILLED and follows the normal request queue process. +@export var should_redemptions_skip_request_queue: bool + +## A Boolean value that determines whether to limit the maximum number of redemptions allowed per live stream (see the `max_per_stream` field). The default is **false**. +@export var is_max_per_stream_enabled: bool + +## The maximum number of redemptions allowed per live stream. Applied only if `is_max_per_stream_enabled` is **true**. The minimum value is 1. +@export var max_per_stream: int + +## A Boolean value that determines whether to limit the maximum number of redemptions allowed per user per stream (see the `max_per_user_per_stream` field). The default is **false**. +@export var is_max_per_user_per_stream_enabled: bool + +## The maximum number of redemptions allowed per user per stream. Applied only if `is_max_per_user_per_stream_enabled` is **true**. The minimum value is 1. +@export var max_per_user_per_stream: int + +## A Boolean value that determines whether to apply a cooldown period between redemptions (see the `global_cooldown_seconds` field for the duration of the cooldown period). The default is **false**. +@export var is_global_cooldown_enabled: bool + +## The cooldown period, in seconds. Applied only if the `is_global_cooldown_enabled` field is **true**. The minimum value is 1; however, the minimum value is 60 for it to be shown in the Twitch UX. +@export var global_cooldown_seconds: int + + +func get_image1() -> Texture: + if image_1: return ImageTexture.create_from_image(image_1) + return default_image_1 + + +func get_image2() -> Texture: + if image_2: return ImageTexture.create_from_image(image_2) + return default_image_2 + + +func get_image4() -> Texture: + if image_4: return ImageTexture.create_from_image(image_4) + return default_image_4 + + +## Needed for the EditorsUndo cause it only supports method references +func emit_changed() -> void: + emit_changed() + + +#region Temporary + +## A Boolean value that determines whether the reward is currently in stock. Is **true** if the reward is in stock. Viewers can’t redeem out of stock rewards. +var is_in_stock: bool +## The number of redemptions redeemed during the current live stream. The number counts against the `max_per_stream_setting` limit. This field is **null** if the broadcaster’s stream isn’t live or _max\_per\_stream\_setting_ isn’t enabled. +var redemptions_redeemed_current_stream: int +## The timestamp of when the cooldown period expires. Is **null** if the reward isn’t in a cooldown state. See the `global_cooldown_setting` field. +var cooldown_expires_at: String + +#endregion diff --git a/addons/twitcher/reward/twitch_reward.gd.uid b/addons/twitcher/reward/twitch_reward.gd.uid new file mode 100644 index 00000000..571fe5c8 --- /dev/null +++ b/addons/twitcher/reward/twitch_reward.gd.uid @@ -0,0 +1 @@ +uid://yxws8ofwmdiy diff --git a/addons/twitcher/reward/twitch_reward_service.gd b/addons/twitcher/reward/twitch_reward_service.gd new file mode 100644 index 00000000..73e5f233 --- /dev/null +++ b/addons/twitcher/reward/twitch_reward_service.gd @@ -0,0 +1,180 @@ +@tool +extends RefCounted + +## Loading and saving of Twitch rewards + +class_name TwitchRewardService + +static var _log: TwitchLogger = TwitchLogger.new("TwitchRewardService") + +var api: TwitchAPI +var media_loader: TwitchMediaLoader + +enum LoadError { + ## All fine + OK, + ## When the reward has no id to load + NO_ID_AVAILABLE, + ## When there is no reward on Twitch side + NO_REWARD_FOUND +} + +enum SaveError { + ## All fine + OK, + ## When the reward was created by another application + REWARD_NOT_OWNED, + ## Something unexpected happend during save + UNKNOWN +} + +enum DeleteError { + ## All fine + OK, + ## The reward to delete didn't had an ID maybe was new reward? + NO_ID, + ## The reward had no broadcaster user saved to it. + NO_BROADCASTER_USER +} + + +func _init(twitch_api: TwitchAPI, twitch_media_loader: TwitchMediaLoader) -> void: + api = twitch_api + media_loader = twitch_media_loader + + +## Loads the reward data inplace from Twitch. +func load_reward(twitch_reward: TwitchReward) -> LoadError: + if twitch_reward.id == "": + _log.e("Can't load %s it has no ID to load" % twitch_reward.title) + return LoadError.NO_ID_AVAILABLE + + var reward: TwitchCustomReward = await _get_custom_reward(twitch_reward) + if reward == null: return LoadError.NO_REWARD_FOUND + _convert_twitch_reward(twitch_reward, reward) + + _log.i("Loaded '%s' from Twitch" % twitch_reward.title) + return LoadError.OK + + +func _convert_twitch_reward(twitch_reward: TwitchReward, reward: TwitchCustomReward) -> void: + twitch_reward.id = reward.id + twitch_reward.title = reward.title + twitch_reward.description = reward.prompt + twitch_reward.cost = reward.cost + twitch_reward.background_color = Color.html(reward.background_color) + twitch_reward.is_enabled = reward.is_enabled + twitch_reward.is_user_input_required = reward.is_user_input_required + twitch_reward.is_max_per_stream_enabled = reward.max_per_stream_setting.is_enabled + twitch_reward.max_per_stream = reward.max_per_stream_setting.max_per_stream + twitch_reward.is_max_per_user_per_stream_enabled = reward.max_per_user_per_stream_setting.is_enabled + twitch_reward.max_per_user_per_stream = reward.max_per_user_per_stream_setting.max_per_user_per_stream + twitch_reward.is_global_cooldown_enabled = reward.global_cooldown_setting.is_enabled + twitch_reward.global_cooldown_seconds = reward.global_cooldown_setting.global_cooldown_seconds + twitch_reward.is_paused = reward.is_paused + twitch_reward.should_redemptions_skip_request_queue = reward.should_redemptions_skip_request_queue + twitch_reward.emit_changed() + + +## Tries to create or update an existing reward. +func save_reward(twitch_reward: TwitchReward) -> SaveError: + # The reward got deleted via UI + if twitch_reward.id != "": + if await _get_custom_reward(twitch_reward) == null: + twitch_reward.id = "" + + if twitch_reward.id == "": + var create = TwitchCreateCustomRewards.Body.new() + create.title = twitch_reward.title + create.prompt = twitch_reward.description + create.cost = twitch_reward.cost + var color = twitch_reward.background_color.to_html(false) + if color: create.background_color = "#" + color + create.is_enabled = twitch_reward.is_enabled + create.is_user_input_required = twitch_reward.is_user_input_required + create.is_max_per_stream_enabled = twitch_reward.is_max_per_stream_enabled + create.max_per_stream = twitch_reward.max_per_stream + create.max_per_user_per_stream = twitch_reward.max_per_user_per_stream + create.is_max_per_user_per_stream_enabled = twitch_reward.is_max_per_user_per_stream_enabled + create.is_global_cooldown_enabled = twitch_reward.is_global_cooldown_enabled + create.global_cooldown_seconds = twitch_reward.global_cooldown_seconds + create.should_redemptions_skip_request_queue = twitch_reward.should_redemptions_skip_request_queue + if not twitch_reward.broadcaster_user: + var current_user: TwitchUser = await TwitchService.get_current_user_via_api(api) + twitch_reward.broadcaster_user = current_user + + var response = await api.create_custom_rewards(create, twitch_reward.broadcaster_user.id) + if response.response.response_code == 200: + var saved_reward: TwitchCustomReward = response.data[0] + twitch_reward.id = saved_reward.id + ResourceSaver.save(twitch_reward) + twitch_reward.emit_changed() + _log.i("Saved the reward %s" % twitch_reward.title) + return SaveError.OK + else: + return SaveError.UNKNOWN + else: + var update = TwitchUpdateCustomReward.Body.new() + update.title = twitch_reward.title + update.prompt = twitch_reward.description + update.cost = twitch_reward.cost + var color = twitch_reward.background_color.to_html(false) + if color: update.background_color = "#" + color + update.is_enabled = twitch_reward.is_enabled + update.is_user_input_required = twitch_reward.is_user_input_required + update.is_max_per_stream_enabled = twitch_reward.is_max_per_stream_enabled + update.max_per_stream = twitch_reward.max_per_stream + update.max_per_user_per_stream = twitch_reward.max_per_user_per_stream + update.is_max_per_user_per_stream_enabled = twitch_reward.is_max_per_user_per_stream_enabled + update.is_global_cooldown_enabled = twitch_reward.is_global_cooldown_enabled + update.global_cooldown_seconds = twitch_reward.global_cooldown_seconds + update.should_redemptions_skip_request_queue = twitch_reward.should_redemptions_skip_request_queue + update.is_paused = twitch_reward.is_paused + var response = await api.update_custom_reward(update, twitch_reward.id, twitch_reward.broadcaster_user.id) + if response.response.response_code == 403: + _log.e("You can only update custom rewards from the application that created it in the first place.") + return SaveError.REWARD_NOT_OWNED + else: + _log.i("Updated the reward %s" % twitch_reward.title) + return SaveError.OK + + +## Deletes a reward on Twitch side. Will also remove the ID when succesfully. +func delete_reward(twitch_reward: TwitchReward) -> DeleteError: + if not twitch_reward.id: + _log.e("Can't delete reward has not id %s" % twitch_reward.title) + return DeleteError.NO_ID + if not twitch_reward.broadcaster_user: + _log.e("Can't delete reward has not broadcaster %s" % twitch_reward.title) + return DeleteError.NO_BROADCASTER_USER + var response: BufferedHTTPClient.ResponseData = await api.delete_custom_reward(twitch_reward.id, twitch_reward.broadcaster_user.id) + if response.response_code <= 300: + _reset_reward(twitch_reward) + else: + var error_message: String = response.response_data.get_string_from_utf8() + push_error("Couldn't delete! Twitch Response (%s): %s" % [response.response_code, error_message]) + + return DeleteError.OK + + +func _reset_reward(twitch_reward: TwitchReward) -> void: + twitch_reward.id = "" + ResourceSaver.save(twitch_reward) + twitch_reward.emit_changed() + + +func _get_custom_reward(twitch_reward: TwitchReward) -> TwitchCustomReward: + if twitch_reward.id == "": + return null + var opt: TwitchGetCustomReward.Opt = TwitchGetCustomReward.Opt.create() + opt.id = [twitch_reward.id] + var reward_response = await api.get_custom_reward(opt, twitch_reward.broadcaster_user.id) + if reward_response.data.is_empty(): + var msg: String = "Can't load Twitch reward %s with id %s from Twitch. It doesn't exist!" % [twitch_reward.title, twitch_reward.id] + _reset_reward(twitch_reward) + _log.e(msg) + if Engine.is_editor_hint(): + EditorInterface.get_editor_toaster().push_toast(msg, EditorToaster.SEVERITY_WARNING) + return null + return reward_response.data[0] + diff --git a/addons/twitcher/reward/twitch_reward_service.gd.uid b/addons/twitcher/reward/twitch_reward_service.gd.uid new file mode 100644 index 00000000..9a6c88a3 --- /dev/null +++ b/addons/twitcher/reward/twitch_reward_service.gd.uid @@ -0,0 +1 @@ +uid://c7o4b65tjmea diff --git a/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd new file mode 100644 index 00000000..9bb8f5ea --- /dev/null +++ b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd @@ -0,0 +1,78 @@ +class_name TwitchChannelPointsCustomRewardRedemptionAddEvent +extends RefCounted + + +const STATUS: PackedStringArray = [ "unknown", "unfulfilled", "fulfilled", "canceled"] + +## The redemption identifier. +var id: String + +## The requested broadcaster ID. +var broadcaster_user_id: String + +## The requested broadcaster login. +var broadcaster_user_login: String + +## The requested broadcaster display name. +var broadcaster_user_name: String + +## User ID of the user that redeemed the reward. +var user_id: String + +## Login of the user that redeemed the reward. +var user_login: String + +## Display name of the user that redeemed the reward. +var user_name: String + +## The user input provided. Empty string if not provided. +var user_input: String + +## Defaults to "unfulfilled". Possible values are "unknown", "unfulfilled", "fulfilled", and "canceled". +var status: TwitchRedemption.Status + +## Basic information about the reward that was redeemed. +var reward: Reward + +## RFC3339 timestamp of when the reward was redeemed. +var redeemed_at: String + + +class Reward extends RefCounted: + ## The reward identifier. + var id: String + ## The reward name. + var title: String + ## The reward cost. + var cost: int + ## The reward description. + var prompt: String + + static func from_json(d: Dictionary) -> Reward: + var reward: Reward = Reward.new() + + reward.id = d.get("id", "") + reward.title = d.get("title", "") + reward.cost = d.get("cost", 0) + reward.prompt = d.get("prompt", "") + + return reward + + +## Creates a new Redemption object from a dictionary. +static func from_json(d: Dictionary) -> TwitchChannelPointsCustomRewardRedemptionAddEvent: + var redemption: TwitchChannelPointsCustomRewardRedemptionAddEvent = TwitchChannelPointsCustomRewardRedemptionAddEvent.new() + + redemption.id = d.get("id", "") + redemption.broadcaster_user_id = d.get("broadcaster_user_id", "") + redemption.broadcaster_user_login = d.get("broadcaster_user_login", "") + redemption.broadcaster_user_name = d.get("broadcaster_user_name", "") + redemption.user_id = d.get("user_id", "") + redemption.user_login = d.get("user_login", "") + redemption.user_name = d.get("user_name", "") + redemption.user_input = d.get("user_input", "") + redemption.status = TwitchRedemption.Status[d.get("status", "unfulfilled").to_upper()] + redemption.redeemed_at = d.get("redeemed_at", "") + redemption.reward = Reward.from_json(d.get("reward", {})) + + return redemption diff --git a/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd.uid b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd.uid new file mode 100644 index 00000000..01260396 --- /dev/null +++ b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_add_event.gd.uid @@ -0,0 +1 @@ +uid://cmkkf4ch2gmgx diff --git a/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd new file mode 100644 index 00000000..8537c738 --- /dev/null +++ b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd @@ -0,0 +1,77 @@ +class_name TwitchChannelPointsCustomRewardRedemptionUpdateEvent +extends RefCounted + +const STATUS: PackedStringArray = [ "unknown", "unfulfilled", "fulfilled", "canceled"] + +## The redemption identifier. +var id: String + +## The requested broadcaster ID. +var broadcaster_user_id: String + +## The requested broadcaster login. +var broadcaster_user_login: String + +## The requested broadcaster display name. +var broadcaster_user_name: String + +## User ID of the user that redeemed the reward. +var user_id: String + +## Login of the user that redeemed the reward. +var user_login: String + +## Display name of the user that redeemed the reward. +var user_name: String + +## The user input provided. Empty string if not provided. +var user_input: String + +## Defaults to "unfulfilled". Possible values are "unknown", "unfulfilled", "fulfilled", and "canceled". +var status: TwitchRedemption.Status + +## Basic information about the reward that was redeemed. +var reward: Reward + +## RFC3339 timestamp of when the reward was redeemed. +var redeemed_at: String + + +class Reward extends RefCounted: + ## The reward identifier. + var id: String + ## The reward name. + var title: String + ## The reward cost. + var cost: int + ## The reward description. + var prompt: String + + static func from_json(d: Dictionary) -> Reward: + var reward: Reward = Reward.new() + + reward.id = d.get("id", "") + reward.title = d.get("title", "") + reward.cost = d.get("cost", 0) + reward.prompt = d.get("prompt", "") + + return reward + + +## Creates a new Redemption object from a dictionary. +static func from_json(d: Dictionary) -> TwitchChannelPointsCustomRewardRedemptionUpdateEvent: + var redemption: TwitchChannelPointsCustomRewardRedemptionUpdateEvent = TwitchChannelPointsCustomRewardRedemptionUpdateEvent.new() + + redemption.id = d.get("id", "") + redemption.broadcaster_user_id = d.get("broadcaster_user_id", "") + redemption.broadcaster_user_login = d.get("broadcaster_user_login", "") + redemption.broadcaster_user_name = d.get("broadcaster_user_name", "") + redemption.user_id = d.get("user_id", "") + redemption.user_login = d.get("user_login", "") + redemption.user_name = d.get("user_name", "") + redemption.user_input = d.get("user_input", "") + redemption.status = TwitchRedemption.Status[d.get("status", "unfulfilled").to_upper()] + redemption.redeemed_at = d.get("redeemed_at", "") + redemption.reward = Reward.from_json(d.get("reward", {})) + + return redemption diff --git a/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd.uid b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd.uid new file mode 100644 index 00000000..8242c6a2 --- /dev/null +++ b/addons/twitcher/reward/twtich_channel_points_custom_reward_redemption_update_event.gd.uid @@ -0,0 +1 @@ +uid://5luvuw6cm2r5 diff --git a/addons/twitcher/sprite_frame_effect.gd b/addons/twitcher/sprite_frame_effect.gd new file mode 100644 index 00000000..5252def5 --- /dev/null +++ b/addons/twitcher/sprite_frame_effect.gd @@ -0,0 +1,116 @@ +extends RichTextEffect + +## Shows spriteframes within richtext label.[br] +## @usage use prepare_message before to load the image and prepare the message accordingly +class_name SpriteFrameEffect + +const TRANSPARENT = preload("res://addons/twitcher/assets/transparent.tres") + +static var regex: RegEx = RegEx.create_from_string("\\[sprite id=(?.*?)\\](?.*?)\\[/sprite\\]") + +## Custom BB Code to use +var bbcode = "sprite" +## To track the emojis +## Key: Id as String Value: Emoji +var cache: Dictionary = {} +## To check if the message got already prepared +var ready: bool +## Save theme for calculating line height +var parent_label: RichTextLabel +## If you don't want to have spaces between images (made for Foolbox <3) +var no_space: bool + +func prepare_message(message: String, parent: RichTextLabel) -> String: + parent_label = parent + var found_matches = regex.search_all(message) as Array[RegExMatch] + + # We are changing the message content and want to preserve the match beginnings. + # So we need to handle them in reverse + found_matches.reverse() + for m: RegExMatch in found_matches: + var path = m.get_string("path") + var id = m.get_string("id") + var resource = ResourceLoader.load(path, "SpriteFrames") as SpriteFrames + if resource == null: continue + var tex = resource.get_frame_texture("default", 0) + var size = tex.get_size() + var start = m.get_start(0) + # Add an empty image to make the correct amount of space + message = message.replace(path, "[img width=%s height=%s]%s[/img]" % [size.x, size.y, TRANSPARENT.resource_path]) + var emoji = _create_emoji(resource) + emoji.name = id + emoji.set_meta("size", size) + cache[id] = emoji + parent.add_child(emoji) + + if no_space: message = message.replace("[/sprite] [sprite", "[/sprite][sprite") + + ready = true + return message + + +func _create_emoji(resource: SpriteFrames): + var node = AnimatedSprite2D.new() + node.sprite_frames = resource + node.centered = true + node.play() + return node + + +func _process_custom_fx(char_fx: CharFXTransform) -> bool: + if not ready: return true + var id = char_fx.env['id'] + # unknown image just ignore + if !cache.has(id): return true + + # Hide the original characters of the [sprite] tag content + # (which should just be the placeholder [img] tag now) + char_fx.visible = false + + # Only position the sprite once, using the first character's info + if char_fx.relative_index != 0: return true + + var node: AnimatedSprite2D = cache[id] + var image_size: Vector2 = node.get_meta("size") # Already stored in prepare_message + + # --- Get Font Metrics from RichTextLabel Theme --- + var font: Font = char_fx.font if char_fx.font else parent_label.get_theme_font(&"normal_font") + var font_size: int = parent_label.get_theme_font_size(&"normal_font_size") + + var font_ascent: float = 0.0 + var font_descent: float = 0.0 # Distance below baseline (positive value) + + if font: + if font_size <= 0: + font_size = parent_label.get_theme_default_font_size() + if font_size <= 0: font_size = 16 # Absolute fallback + + # Get ascent and descent, scaled by the RichTextLabel's scale + font_ascent = font.get_ascent(font_size) * parent_label.scale.y + font_descent = font.get_descent(font_size) * parent_label.scale.y + else: + # Fallback estimation + if font_size <= 0: font_size = 16 + var scaled_font_size = float(font_size) * parent_label.scale.y + font_ascent = scaled_font_size * 0.8 # Estimate + font_descent = scaled_font_size * 0.2 # Estimate + printerr("SpriteFrameEffect: Could not determine font for ascent/descent calculation, using estimate.") + + # --- Calculate Position --- + var baseline_origin: Vector2 = char_fx.transform.get_origin() + + # Horizontal center (remains the same) + var center_x: float = baseline_origin.x + image_size.x / 2.0 + + # Vertical position: Align sprite center with the vertical center of the font glyph box. + # The top of the font box is at baseline_origin.y - font_ascent. + # The bottom of the font box is at baseline_origin.y + font_descent. + # The total height of the font box is font_ascent + font_descent. + # The center relative to the baseline is baseline - ascent + (total_height / 2) + # = baseline - ascent + (ascent + descent) / 2 + # = baseline - (ascent / 2) + (descent / 2) + # = baseline - (ascent - descent) / 2.0 + var center_y: float = baseline_origin.y - (font_ascent - font_descent) / 2.0 + + node.position = Vector2(center_x, center_y) + return true diff --git a/addons/twitcher/sprite_frame_effect.gd.uid b/addons/twitcher/sprite_frame_effect.gd.uid new file mode 100644 index 00000000..af9dcf38 --- /dev/null +++ b/addons/twitcher/sprite_frame_effect.gd.uid @@ -0,0 +1 @@ +uid://bih5dayr8n246 diff --git a/addons/twitcher/twitch_data.gd b/addons/twitcher/twitch_data.gd new file mode 100644 index 00000000..065a655a --- /dev/null +++ b/addons/twitcher/twitch_data.gd @@ -0,0 +1,33 @@ +extends Resource + +## Base class to track which data got changed. +class_name TwitchData + +## Contains all values that got actually changed +var _tracked: Dictionary[StringName, Variant] = {} + + +func track_data(property: StringName, value: Variant) -> bool: + if value == null: + _tracked.erase(property) + elif value is Array: + var serialized_value = [] + for value_entry in value: + if "to_dict" in value_entry: + serialized_value.append(value_entry.to_dict()) + else: + serialized_value.append(value_entry) + _tracked[property] = serialized_value + elif typeof(value) == TYPE_OBJECT && value.is_class(self.get_class()): + _tracked[property] = value.to_dict() + else: + _tracked[property] = value + return false + + +func to_dict() -> Dictionary[StringName, Variant]: + return _tracked + + +func to_json() -> String: + return JSON.stringify(to_dict()) diff --git a/addons/twitcher/twitch_data.gd.uid b/addons/twitcher/twitch_data.gd.uid new file mode 100644 index 00000000..1301e63c --- /dev/null +++ b/addons/twitcher/twitch_data.gd.uid @@ -0,0 +1 @@ +uid://bnqet63fovbgp diff --git a/addons/twitcher/twitch_oauth_setting.tres b/addons/twitcher/twitch_oauth_setting.tres new file mode 100644 index 00000000..c3959e54 --- /dev/null +++ b/addons/twitcher/twitch_oauth_setting.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="OAuthSetting" format=3 uid="uid://ry64ckbckh6m"] + +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="1_54bgt"] +[ext_resource type="Script" uid="uid://00xbijwpi8xa" path="res://addons/twitcher/lib/oOuch/oauth_setting.gd" id="2_wroyb"] + +[resource] +script = ExtResource("2_wroyb") +token_url = "https://id.twitch.tv/oauth2/token" +authorization_url = "https://id.twitch.tv/oauth2/authorize" +device_authorization_url = "https://id.twitch.tv/oauth2/device" +cache_file = "user://auth.conf" +client_id = "tlbzmtdy2itfeazyuumchynsez6pqv" +client_secret = "I+Psn7ZIjTlehNFSoJTbk0sLL9xWqKDelHLDMcvYZwQ=" diff --git a/addons/twitcher/twitch_service.gd b/addons/twitcher/twitch_service.gd new file mode 100644 index 00000000..55230f49 --- /dev/null +++ b/addons/twitcher/twitch_service.gd @@ -0,0 +1,412 @@ +@icon("res://addons/twitcher/assets/service-icon.svg") +@tool +extends Twitcher + +## Access to the Twitch API. Combines all the stuff the library provides. +## Makes some actions easier to use. +class_name TwitchService + +const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd") +## When the poll doesn't end after the offical endtime + POLL_TIMEOUT_MS. The wait loop for poll end +## event will be stopped to prevent endless loops. +const POLL_TIMEOUT_MS: int = 30000 + +static var _log: TwitchLogger = TwitchLogger.new("TwitchService") + +static var instance: TwitchService + +@export var oauth_setting: OAuthSetting: + set(val): + if oauth_setting != null: + oauth_setting.changed.disconnect(update_configuration_warnings) + oauth_setting = val + if val != null: + oauth_setting.changed.connect(update_configuration_warnings) + _set_in_child("oauth_setting", val) + update_configuration_warnings() +@export var scopes: OAuthScopes: + set(val): + scopes = val + if val != null: + _set_in_child("scopes", val) + update_configuration_warnings() +@export var token: OAuthToken: + set(val): + token = val + if val != null: + _set_in_child("token", val) + update_configuration_warnings() + +@onready var auth: TwitchAuth +@onready var eventsub: TwitchEventsub +@onready var api: TwitchAPI +@onready var irc: TwitchIRC +@onready var media_loader: TwitchMediaLoader + +var _user_cache: Dictionary[String, TwitchUser] = {} + +## Cache for the current user so that no roundtrip has to be done every time get_current_user will be called +var _current_user: TwitchUser + +var _commands: Dictionary[String, TwitchCommand] = {} + +func _init() -> void: + child_entered_tree.connect(_on_child_entered) + child_exiting_tree.connect(_on_child_exiting) + + +func _ready() -> void: + _log.d("is ready") + if not is_instance_valid(token): token = TwitchEditorSettings.game_oauth_token + if not is_instance_valid(oauth_setting): oauth_setting = TwitchEditorSettings.game_oauth_setting + + +func _enter_tree() -> void: + if instance == null: instance = self + + +func _exit_tree() -> void: + if instance == self: instance = null + + +func _on_child_entered(node: Node) -> void: + if node is TwitchAuth: auth = node + if node is TwitchAPI: api = node + if node is TwitchEventsub: eventsub = node + if node is TwitchIRC: irc = node + if node is TwitchMediaLoader: media_loader = node + + if "token" in node && token != null: + node.token = token + if "scopes" in node && scopes != null: + node.scopes = scopes + if "oauth_setting" in node && oauth_setting != null: + node.oauth_setting = oauth_setting + if node.has_signal(&"unauthenticated"): + node.unauthenticated.connect(_on_unauthenticated) + update_configuration_warnings() + + +func _set_in_child(property: String, value: Variant) -> void: + for child in get_children(): + if property in child: child[property] = value + + +func _on_child_exiting(node: Node) -> void: + if node is TwitchAuth: auth = null + if node is TwitchAPI: api = null + if node is TwitchEventsub: eventsub = null + if node is TwitchIRC: irc = null + if node is TwitchMediaLoader: media_loader = null + + if node.has_signal(&"unauthenticated"): + node.unauthenticated.disconnect(_on_unauthenticated) + update_configuration_warnings() + + +## Call this to setup the complete Twitch integration whenever you need. +## It boots everything up this Lib supports. +func setup() -> bool: + if is_instance_valid(auth): + if not await auth.authorize(): return false + elif not token.is_token_valid(): + push_error("Authorization Node got removed, can't setup twitch service") + return false + await propagate_call(&"do_setup") + for child in get_children(): + if child.has_method(&"wait_setup"): + await child.wait_setup() + + _log.i("TwitchService setup") + return true + + +## Removes the token so that +func unsetup() -> void: + await propagate_call(&"do_unsetup") + for child in get_children(): + if child.has_method(&"wait_unsetup"): + await child.wait_unsetup() + +func do_unsetup() -> void: + _current_user = null + +## Checks if the correctly setup +func is_configured() -> bool: + return _get_configuration_warnings().is_empty() + + +func _get_configuration_warnings() -> PackedStringArray: + var result: PackedStringArray = [] + if oauth_setting == null: + result.append("OAuthSetting Resource is missing") + else: + var oauth_setting_problems : PackedStringArray = oauth_setting.get_valididation_problems() + if not oauth_setting_problems.is_empty(): + result.append("OAuthSetting Resource is invalid") + result.append_array(oauth_setting_problems) + if scopes == null: + result.append("OAuthScopes Resource is missing") + if token == null: + result.append("OAuthToken Resource is missing") + return result + + +func _on_unauthenticated() -> void: + auth.authorize() + +# +# Convinient Proxy Methods +# +#region User + + +## Get data about a user by USER_ID see get_user for by username +func get_user_by_id(user_id: String) -> TwitchUser: + if _user_cache.has(user_id): return _user_cache[user_id] + if api == null: + _log.e("Please setup a TwitchAPI Node into TwitchService.") + return null + if user_id == null || user_id == "": return null + var opt = TwitchGetUsers.Opt.new() + opt.id = [user_id] as Array[String] + var user_data : TwitchGetUsers.Response = await api.get_users(opt) + if user_data.data.is_empty(): return null + var user: TwitchUser = user_data.data[0] + _user_cache[user_id] = user + return user + + +## Get data about a user by USERNAME see get_user_by_id for by user_id +func get_user(username: String) -> TwitchUser: + username = username.trim_prefix("@") + if _user_cache.has(username): return _user_cache[username] + if api == null: + _log.e("Please setup a TwitchAPI Node into TwitchService.") + return null + var opt = TwitchGetUsers.Opt.new() + opt.login = [username] as Array[String] + var user_data : TwitchGetUsers.Response = await api.get_users(opt) + if user_data.data.is_empty(): + _log.e("Username was not found: %s" % username) + return null + var user: TwitchUser = user_data.data[0] + _user_cache[username] = user + return user + + + +## Get data about a currently authenticated user (caches the value) +func get_current_user() -> TwitchUser: + if _current_user != null: + return _current_user + _current_user = await get_current_user_via_api(api) + return _current_user + + +## Helper Method to be used in the Editor Scripts +static func get_current_user_via_api(api: TwitchAPI) -> TwitchUser: + if api == null: + _log.e("Please setup a TwitchAPI Node into TwitchService.") + return null + + var user_data : TwitchGetUsers.Response = await api.get_users(null) + var user: TwitchUser = user_data.data[0] + return user + + +## Get the image of an user +func load_profile_image(user: TwitchUser) -> ImageTexture: + return await media_loader.load_profile_image(user) + +#endregion +#region EventSub + + +## Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ for details on +## which API versions are available and which conditions are required. +func subscribe_event(definition: TwitchEventsubDefinition, conditions: Dictionary) -> TwitchEventsubConfig: + if definition == null: + _log.e("TwitchEventsubDefinition is null") + return + + var config = TwitchEventsubConfig.create(definition, conditions) + await eventsub.subscribe(config) + return config + + +## Waits for connection to eventsub. Eventsub is ready to subscribe events. +func wait_for_eventsub_connection() -> void: + if eventsub == null: + _log.e("TwitchEventsub Node is missing") + return + await eventsub.wait_for_connection() + + +## Returns all of the eventsub subscriptions (variable is a copy so you can freely modify it) +func get_subscriptions() -> Array[TwitchEventsubConfig]: + if eventsub == null: + _log.e("TwitchEventsub Node is missing") + return [] + return eventsub.get_subscriptions() + +#endregion + +#region Chat + +func chat(message: String, broadcaster: TwitchUser = null, sender: TwitchUser = null) -> void: + var current_user = await get_current_user() + if not sender: + if not current_user: return + sender = current_user + if not broadcaster: + if not current_user: return + broadcaster = current_user + var body = TwitchSendChatMessage.Body.create(broadcaster.id, sender.id, message) + api.send_chat_message(body) + + +## Sends out a shoutout to a specific user +func shoutout(user: TwitchUser, broadcaster: TwitchUser = null, moderator: TwitchUser = null) -> void: + var current_user: TwitchUser = await get_current_user() + + if not broadcaster: + if not current_user: return + broadcaster = current_user + + if not moderator: + if not current_user: return + moderator = current_user + api.send_a_shoutout(broadcaster.id, moderator.id, user.id) + + +## Sends a announcement message to the chat +func announcment(message: String, color: TwitchAnnouncementColor = TwitchAnnouncementColor.PRIMARY, broadcaster: TwitchUser = null, moderator: TwitchUser = null): + var current_user: TwitchUser = await get_current_user() + if not broadcaster: + if not current_user: return + broadcaster = current_user + + if not moderator: + if not current_user: return + moderator = current_user + + var body = TwitchSendChatAnnouncement.Body.new() + body.message = message + body.color = color.value + api.send_chat_announcement(body, moderator.id, broadcaster.id) + + +## Add a new command handler and register it for a command. +## The callback will receive [code]from_username: String, info: TwitchCommandInfo, args: PackedStringArray[/code][br] +## Args are optional depending on the configuration.[br] +## args_max == -1 => no upper limit for arguments +func add_command(command: String, callback: Callable, args_min: int = 0, args_max: int = -1, + permission_level : TwitchCommand.PermissionFlag = TwitchCommand.PermissionFlag.EVERYONE, + where : TwitchCommand.WhereFlag = TwitchCommand.WhereFlag.CHAT, user_cooldown: float = 0, + global_cooldown: float = 0) -> TwitchCommand: + var command_node = TwitchCommand.new() + command_node.command = command + command_node.command_received.connect(callback) + command_node.args_min = args_min + command_node.args_max = args_max + command_node.permission_level = permission_level + command_node.where = where + command_node.user_cooldown = user_cooldown + command_node.global_cooldown = global_cooldown + add_child(command_node) + _log.i("Register command %s" % command) + return command_node + + +## Removes a command +func remove_command(command: String) -> void: + _log.i("Remove command %s" % command) + var command_node: TwitchCommand = _commands.get(command, null) + if command_node != null: + command_node.queue_free() + _commands.erase(command) + + +## Whispers to another user. +## @deprecated not supported by twitch anymore +func whisper(message: String, username: String) -> void: + _log.e("Whipser from bots aren't supported by Twitch anymore. See https://dev.twitch.tv/docs/irc/chat-commands/ at /w") + + +## Returns the definition of emotes for given channel or for the global emotes. +## Key: EmoteID as String | Value: TwitchGlobalEmote / TwitchChannelEmote +func get_emotes_data(channel_id: String = "global") -> Dictionary: + return await media_loader.get_cached_emotes(channel_id) + + +## Returns the definition of badges for given channel or for the global bages. +## Key: category / versions / badge_id | Value: TwitchChatBadge +func get_badges_data(channel_id: String = "global") -> Dictionary[String, TwitchChatBadge]: + return await media_loader.get_cached_badges(channel_id) + + +## Gets the requested emotes. +## Key: EmoteID as String | Value: SpriteFrame +func get_emotes(ids: Array[String]) -> Dictionary[String, SpriteFrames]: + return await media_loader.get_emotes(ids) + + +## Gets the requested emotes in the specified theme, scale and type. +## Loads from cache if possible otherwise downloads and transforms them. +## Key: TwitchEmoteDefinition | Value SpriteFrames +func get_emotes_by_definition(emotes: Array[TwitchEmoteDefinition]) -> Dictionary[TwitchEmoteDefinition, SpriteFrames]: + return await media_loader.get_emotes_by_definition(emotes) + + +func poll(title: String, choices: Array[String], duration: int = 60, channel_points_voting_enabled: bool = false, channel_points_per_vote: int = 1000, broadcaster_id: String = "") -> Dictionary: + if broadcaster_id == "": broadcaster_id = _current_user.id + var body_choices: Array[TwitchCreatePoll.BodyChoices] = [] + for choice: String in choices: + var body_choice = TwitchCreatePoll.BodyChoices.create(choice) + body_choices.append(body_choice) + duration = clamp(duration, 15, 1800) + var poll_body: TwitchCreatePoll.Body = TwitchCreatePoll.Body.create(broadcaster_id, title, body_choices, duration) + if channel_points_voting_enabled: + poll_body.channel_points_per_vote = channel_points_per_vote + poll_body.channel_points_voting_enabled = channel_points_voting_enabled + var poll_response: TwitchCreatePoll.Response = await api.create_poll(poll_body) + if poll_response.response.response_code != 200: + var error_message: String = poll_response.response.response_data.get_string_from_utf8() + push_error("Can't create poll response cause of ", error_message) + return {} + var poll: TwitchPoll = poll_response.data[0] + var poll_end_time: int = Time.get_ticks_msec() + duration * 1000 + POLL_TIMEOUT_MS + var event: TwitchEventsub.Event + if eventsub && eventsub.has_subscription(TwitchEventsubDefinition.CHANNEL_POLL_END, {&"broadcaster_user_id": broadcaster_id}): + var poll_ended: bool + while not poll_ended: + if poll_end_time < Time.get_ticks_msec(): + return {} + event = await eventsub.event_received + if event.type != TwitchEventsubDefinition.CHANNEL_POLL_END: continue + if event.data[&"id"] != poll.id: continue + break + else: + _log.i("Can't wait for poll end. Either eventsub is not set ot it is not listenting to ending polls") + return {} + return event.data + +#endregion +#region Cheermotes + +## Returns the data of the Cheermotes. +func get_cheermote_data() -> Array[TwitchCheermote]: + if media_loader == null: + _log.e("TwitchMediaLoader was not set within %s" % get_tree_string()) + return [] + await media_loader.preload_cheemote() + return media_loader.all_cheermotes() + + +## Returns all cheertiers in form of: +## Key: TwitchCheermote.Tiers | Value: SpriteFrames +func get_cheermotes(definition: TwitchCheermoteDefinition) -> Dictionary: + return await media_loader.get_cheermotes(definition) + +#endregion diff --git a/addons/twitcher/twitch_service.gd.uid b/addons/twitcher/twitch_service.gd.uid new file mode 100644 index 00000000..8f3e366d --- /dev/null +++ b/addons/twitcher/twitch_service.gd.uid @@ -0,0 +1 @@ +uid://i8st3lv0lidh diff --git a/addons/twitcher/twitch_service.tscn b/addons/twitcher/twitch_service.tscn new file mode 100644 index 00000000..4503cd11 --- /dev/null +++ b/addons/twitcher/twitch_service.tscn @@ -0,0 +1,56 @@ +[gd_scene load_steps=14 format=3 uid="uid://djt5lvmwxbq4a"] + +[ext_resource type="Script" uid="uid://i8st3lv0lidh" path="res://addons/twitcher/twitch_service.gd" id="1_6jm7q"] +[ext_resource type="Script" uid="uid://cw30cwveway65" path="res://addons/twitcher/generated/twitch_api.gd" id="1_yq54x"] +[ext_resource type="Resource" uid="uid://ry64ckbckh6m" path="res://addons/twitcher/twitch_oauth_setting.tres" id="2_v2pwk"] +[ext_resource type="Resource" uid="uid://fcmfkstye4bq" path="res://addons/twitcher/auth/preset_overlay_scopes.tres" id="3_n5rs1"] +[ext_resource type="Script" uid="uid://blmhj3j00yk45" path="res://addons/twitcher/eventsub/twitch_eventsub.gd" id="3_x3t7l"] +[ext_resource type="Resource" uid="uid://m7epy882axmp" path="res://addons/twitcher/default_oauth_token.tres" id="4_j0p3d"] +[ext_resource type="Script" uid="uid://bf0wi70haua35" path="res://addons/twitcher/lib/oOuch/oauth.gd" id="8_v2pwk"] +[ext_resource type="Script" uid="uid://blnbogtrshw4r" path="res://addons/twitcher/auth/twitch_token_handler.gd" id="9_n5rs1"] +[ext_resource type="Script" uid="uid://iv0mgv0lu8b0" path="res://addons/twitcher/auth/twitch_auth.gd" id="10_bqnqx"] +[ext_resource type="Texture2D" uid="uid://g1dbcjksbotw" path="res://addons/twitcher/assets/fallback_texture.tres" id="11_j0p3d"] +[ext_resource type="Script" uid="uid://d4lyup0vy1wtu" path="res://addons/twitcher/media/twitch_media_loader.gd" id="12_2tq2g"] +[ext_resource type="Script" uid="uid://6v8jnfjwbnhm" path="res://addons/twitcher/media/twitch_image_transformer.gd" id="12_uvps8"] + +[sub_resource type="Resource" id="Resource_nmcm3"] +script = ExtResource("12_uvps8") +fallback_texture = ExtResource("11_j0p3d") + +[node name="TwitchService" type="Node"] +script = ExtResource("1_6jm7q") +oauth_setting = ExtResource("2_v2pwk") +scopes = ExtResource("3_n5rs1") +token = ExtResource("4_j0p3d") + +[node name="EventSub" type="Node" parent="." node_paths=PackedStringArray("api")] +script = ExtResource("3_x3t7l") +api = NodePath("../API") +scopes = ExtResource("3_n5rs1") + +[node name="API" type="Node" parent="."] +script = ExtResource("1_yq54x") +token = ExtResource("4_j0p3d") +oauth_setting = ExtResource("2_v2pwk") + +[node name="Auth" type="Node" parent="."] +script = ExtResource("10_bqnqx") +oauth_setting = ExtResource("2_v2pwk") +token = ExtResource("4_j0p3d") +scopes = ExtResource("3_n5rs1") + +[node name="OAuth" type="Node" parent="Auth" node_paths=PackedStringArray("token_handler")] +script = ExtResource("8_v2pwk") +oauth_setting = ExtResource("2_v2pwk") +scopes = ExtResource("3_n5rs1") +token_handler = NodePath("../TokenHandler") + +[node name="TokenHandler" type="Node" parent="Auth"] +script = ExtResource("9_n5rs1") +oauth_setting = ExtResource("2_v2pwk") +token = ExtResource("4_j0p3d") + +[node name="MediaLoader" type="Node" parent="." node_paths=PackedStringArray("api")] +script = ExtResource("12_2tq2g") +api = NodePath("../API") +image_transformer = SubResource("Resource_nmcm3") diff --git a/addons/twitcher/twitcher.gd b/addons/twitcher/twitcher.gd new file mode 100644 index 00000000..4bb3f1c0 --- /dev/null +++ b/addons/twitcher/twitcher.gd @@ -0,0 +1,5 @@ +@icon("res://addons/twitcher/assets/twitcher-icon.svg") +extends Node + +## Parent class to group all Twitcher relevant nodes. +class_name Twitcher diff --git a/addons/twitcher/twitcher.gd.uid b/addons/twitcher/twitcher.gd.uid new file mode 100644 index 00000000..ccc22d8a --- /dev/null +++ b/addons/twitcher/twitcher.gd.uid @@ -0,0 +1 @@ +uid://dv7ewgsctdjqr diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 00000000..39373808 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource(1) diff --git a/general_commands.gd b/general_commands.gd new file mode 100644 index 00000000..58e4b460 --- /dev/null +++ b/general_commands.gd @@ -0,0 +1,15 @@ +extends Node + +func _ready() -> void: + var nodes := get_children() + for node in nodes: + if node is TwitchCommand: + var cmd: TwitchCommand = node + if self.has_method("_handle_%s" % cmd.name.to_camel_case()): + cmd.command_received.connect(Callable(self, "_handle_%s" % cmd.name.to_camel_case())) + if self.has_method("_cooldown_%s" % cmd.name.to_camel_case()): + cmd.command_received.connect(Callable(self, "_cooldown_%s" % cmd.name.to_camel_case())) + +func _handle_test(_from_username: String, info: TwitchCommandInfo, _args: PackedStringArray) -> void: + var msg: TwitchChatMessage = info.original_message + Globals.twitcher.reply_message("Test Command Works!", msg.message_id) diff --git a/general_commands.gd.uid b/general_commands.gd.uid new file mode 100644 index 00000000..b9d3d008 --- /dev/null +++ b/general_commands.gd.uid @@ -0,0 +1 @@ +uid://cv06lthuq2c2o diff --git a/icon.png b/icon.png new file mode 100644 index 00000000..8a3cd7b3 Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 00000000..0aec3ce7 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://iwfg8yfxwjom" +path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/lib/app_context.gd b/lib/app_context.gd new file mode 100644 index 00000000..f5a99f9e --- /dev/null +++ b/lib/app_context.gd @@ -0,0 +1,7 @@ +extends Context +class_name OverlayContext + +var chatters: DbSet + +func _init() -> void: + chatters = DbSet.new(Chatter) diff --git a/lib/app_context.gd.uid b/lib/app_context.gd.uid new file mode 100644 index 00000000..dd71fe54 --- /dev/null +++ b/lib/app_context.gd.uid @@ -0,0 +1 @@ +uid://d2vf38mh2l435 diff --git a/lib/chat_manager.gd b/lib/chat_manager.gd new file mode 100644 index 00000000..2dc679bb --- /dev/null +++ b/lib/chat_manager.gd @@ -0,0 +1,53 @@ +@tool +extends Node + +signal first_seen_chat(user: TwitchUser, chatter: Chatter, msg: TwitchChatMessage) +signal first_chat(user: TwitchUser, chatter: Chatter, msg: TwitchChatMessage) +signal chat_message(user: TwitchUser, msg: TwitchChatMessage) + +var chatters: Dictionary[TwitchUser, Chatter] = {} +var _log: TwitchLogger = TwitchLogger.new("ChatManager") + + +func _ready() -> void: + if Engine.is_editor_hint(): return + while not Globals.twitcher: + await get_tree().process_frame + + Globals.twitcher.chat.message_received.connect(_handle_message) + + +func _handle_message(message: TwitchChatMessage) -> void: + var id := message.chatter_user_id + var user: TwitchUser + var chatter: Chatter + var first_msg: bool = false + var first_seen: bool = false + # Do we have the chatter this session? + if chatters.keys().any(func(x: TwitchUser): return x.id == id): + var res := chatters.keys().filter(func(x: TwitchUser): return x.id == id) + user = res[0] + chatter = chatters[user] + else: + user = await Globals.twitcher.get_user_by_id(id) + if not user: + _log.e("Failed to fetch user by id: %s" % [id]) + return + # Do we have this chatter in our database? + chatter = Globals.context.chatters.find_one(Condition.new().equal("twitch_id", id)) + if not chatter: + chatter = Chatter.new() + chatter.twitch_id = id + chatter.first_seen = Time.get_unix_time_from_system() + chatter.last_seen = chatter.first_seen + Globals.context.chatters.append(chatter) + first_seen = true + else: + chatter.last_seen = Time.get_unix_time_from_system() + chatter.save() + first_msg = true + chatters[user] = chatter + + if first_seen: first_seen_chat.emit(user, chatter, message) + if first_msg: first_chat.emit(user, chatter, message) + chat_message.emit(user, chatter, message) diff --git a/lib/chat_manager.gd.uid b/lib/chat_manager.gd.uid new file mode 100644 index 00000000..0cd94831 --- /dev/null +++ b/lib/chat_manager.gd.uid @@ -0,0 +1 @@ +uid://ct1s5eymb8mns diff --git a/lib/debugger_window.gd b/lib/debugger_window.gd new file mode 100644 index 00000000..8b5000c0 --- /dev/null +++ b/lib/debugger_window.gd @@ -0,0 +1,54 @@ +extends PanelContainer +class_name DebuggerWindow + +static var instance: DebuggerWindow +@onready var debug_list: VBoxContainer = %DebugList + +var _debuggers: Dictionary[String, Debugger] = {} + +func _ready() -> void: + if not instance: + instance = self + +func add_debugger(label: String, proc: Callable, async: bool = false) -> void: + var dbgr := Debugger.new(label, proc, async) + debug_list.add_child(dbgr) + _debuggers[name] = dbgr + +func remove_debugger(label: String) -> void: + if not _debuggers.has(label): return + _debuggers[label].queue_free() + _debuggers.erase(label) + +func enable_debugger(label: String) -> void: + if not _debuggers.has(label): return + _debuggers[label].enabled = true + +func disable_debugger(label: String) -> void: + if not _debuggers.has(label): return + _debuggers[label].enabled = false + +class Debugger: + extends HBoxContainer + var _label: Label + var _field: Label + var _proc: Callable + var _async: bool + var enabled: bool = false + + func _init(label: String, proc: Callable, async: bool = false) -> void: + _async = async + _proc = proc + _label = Label.new() + _field = Label.new() + _label.text = label + add_child(_label) + add_child(_field) + _field.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + func _process(_delta: float) -> void: + if enabled: + if _async: + _field.text = str(await _proc.call()) + else: + _field.text = str(_proc.call()) diff --git a/lib/debugger_window.gd.uid b/lib/debugger_window.gd.uid new file mode 100644 index 00000000..c7a4c3bf --- /dev/null +++ b/lib/debugger_window.gd.uid @@ -0,0 +1 @@ +uid://dx87h5yv6t5tk diff --git a/lib/debugger_window.tscn b/lib/debugger_window.tscn new file mode 100644 index 00000000..9798c5b5 --- /dev/null +++ b/lib/debugger_window.tscn @@ -0,0 +1,25 @@ +[gd_scene format=3 uid="uid://cvbrjmhk4f146"] + +[ext_resource type="Script" uid="uid://dx87h5yv6t5tk" path="res://lib/debugger_window.gd" id="1_80gsw"] + +[sub_resource type="LabelSettings" id="LabelSettings_rkxbk"] + +[node name="DebuggerWindow" type="PanelContainer" unique_id=2039594777] +custom_minimum_size = Vector2(300, 200) +script = ExtResource("1_80gsw") + +[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=79592823] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer" unique_id=1980761690] +layout_mode = 2 +text = "DEBUGGER WINDOW" +label_settings = SubResource("LabelSettings_rkxbk") + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer" unique_id=1571656376] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="DebugList" type="VBoxContainer" parent="VBoxContainer/ScrollContainer" unique_id=1758953873] +unique_name_in_owner = true +layout_mode = 2 diff --git a/lib/globals.gd b/lib/globals.gd new file mode 100644 index 00000000..6e64f668 --- /dev/null +++ b/lib/globals.gd @@ -0,0 +1,23 @@ +extends Node + +var twitcher: TwitcherExtended +var context: OverlayContext +var settings: OverlaySettings + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + context = OverlayContext.new() + context.setup() + + context.open_db("user://overlay.db") + context.ensure_tables() + if FileAccess.file_exists("user://settings.tres"): + settings = load("user://settings.tres") + else: + settings = OverlaySettings.new() + +func _exit_tree() -> void: + save_settings() + +func save_settings() -> void: + ResourceSaver.save(settings, "user://settings.tres") diff --git a/lib/globals.gd.uid b/lib/globals.gd.uid new file mode 100644 index 00000000..16d78238 --- /dev/null +++ b/lib/globals.gd.uid @@ -0,0 +1 @@ +uid://cuap0k5jagdtj diff --git a/lib/models/chatter.gd b/lib/models/chatter.gd new file mode 100644 index 00000000..628f8e13 --- /dev/null +++ b/lib/models/chatter.gd @@ -0,0 +1,34 @@ +extends SQLiteObject +class_name Chatter + +enum ChatterLevel +{ + NEW, + NORMAL, + REGULAR, + DEVOTEE, + VIP, + MOD, + STREAMER +} + +@export var id: int +@export var twitch_id: String +@export var nickname: String = "" +@export var known_engine: String = "" +@export var game_lists: Dictionary[String, String] = {} +@export var is_indie_game_dev: bool = false +@export var is_on_team: bool = false +@export var level: ChatterLevel = ChatterLevel.NEW +@export var auto_shoutout: bool = false +@export var shoutout_as_devteam: bool = false +@export var notes: String = "" +@export var scores: Dictionary[String, int] = {} +@export var extra_data: Dictionary[String, Variant] = {} +@export var first_seen: float = 0.0 +@export var last_seen: float = 0.0 + +static func _setup() -> void: + set_table_name(Chatter, "chatters") + set_column_flags(Chatter, "id", Flags.PRIMARY_KEY|Flags.AUTO_INCREMENT|Flags.NOT_NULL) + set_column_flags(Chatter, "twitch_id", Flags.NOT_NULL) diff --git a/lib/models/chatter.gd.uid b/lib/models/chatter.gd.uid new file mode 100644 index 00000000..2be63c6f --- /dev/null +++ b/lib/models/chatter.gd.uid @@ -0,0 +1 @@ +uid://vfrg17drxmif diff --git a/lib/overlay_settings.gd b/lib/overlay_settings.gd new file mode 100644 index 00000000..e17f42db --- /dev/null +++ b/lib/overlay_settings.gd @@ -0,0 +1,4 @@ +extends Resource +class_name OverlaySettings + +@export var auto_connect: bool = false diff --git a/lib/overlay_settings.gd.uid b/lib/overlay_settings.gd.uid new file mode 100644 index 00000000..97e9411f --- /dev/null +++ b/lib/overlay_settings.gd.uid @@ -0,0 +1 @@ +uid://dj0xfuvwdqryy diff --git a/lib/state_machine/state.gd b/lib/state_machine/state.gd new file mode 100644 index 00000000..fa410ba5 --- /dev/null +++ b/lib/state_machine/state.gd @@ -0,0 +1,16 @@ +extends Node +class_name State + +signal transitioned(state: State, new_state_name: String) + +func _enter() -> void: + pass + +func _exit() -> void: + pass + +func _update(_delta: float) -> void: + pass + +func _physics_update(_delta: float) -> void: + pass diff --git a/lib/state_machine/state.gd.uid b/lib/state_machine/state.gd.uid new file mode 100644 index 00000000..f4bbac20 --- /dev/null +++ b/lib/state_machine/state.gd.uid @@ -0,0 +1 @@ +uid://cb1uc3nlv2mgj diff --git a/lib/state_machine/state_machine.gd b/lib/state_machine/state_machine.gd new file mode 100644 index 00000000..9f7fbf8b --- /dev/null +++ b/lib/state_machine/state_machine.gd @@ -0,0 +1,44 @@ +extends Node +class_name StateMachine + +@export var initial_state: State + +signal state_changed(new_state: State) + +var current_state: State +var states: Dictionary[String, State] = {} + +func _ready() -> void: + for child in get_children(): + if child is State: + states[child.name.to_lower()] = child + child.transitioned.connect(_handle_child_transition) + + if initial_state: + initial_state._enter() + current_state = initial_state + +func _process(delta) -> void: + if current_state: + current_state._update(delta) + +func _physics_process(delta: float) -> void: + if current_state: + current_state._physics_update(delta) + +func _handle_child_transition(state: State, new_state_name: String) -> void: + if state != current_state: + return + + var new_state: State = states.get(new_state_name.to_lower(), null) + + if not new_state: + return + + if current_state: + current_state._exit() + + new_state._enter() + + current_state = new_state + state_changed.emit(new_state) diff --git a/lib/state_machine/state_machine.gd.uid b/lib/state_machine/state_machine.gd.uid new file mode 100644 index 00000000..730e7fc3 --- /dev/null +++ b/lib/state_machine/state_machine.gd.uid @@ -0,0 +1 @@ +uid://c16ty2yx4qkvu diff --git a/lib/twitcher_extended.zip b/lib/twitcher_extended.zip new file mode 100644 index 00000000..4e2f3b5f Binary files /dev/null and b/lib/twitcher_extended.zip differ diff --git a/lib/twitcher_extended/chatbot_authorization.gd b/lib/twitcher_extended/chatbot_authorization.gd new file mode 100644 index 00000000..a8a65450 --- /dev/null +++ b/lib/twitcher_extended/chatbot_authorization.gd @@ -0,0 +1,117 @@ +@tool +extends Twitcher +class_name ChatbotAuthorization + +@export var token: OAuthToken: + set(value): + token = value + if auth: + auth.token = token + auth.token_handler.token = token +@export var oauth_setting: OAuthSetting +@export var scopes: OAuthScopes = preload("res://addons/twitcher/chat/twitch_bot_scopes.tres") + +var api: TwitchAPI +var auth: TwitchAuth + +@export_tool_button("Reset Token") var _reset_token := func() -> void: + token.remove_tokens() + +var _config_file: ConfigFile = ConfigFile.new() + +signal _waiting_for_authentication + +func _init() -> void: + child_entered_tree.connect(_handle_child_entered) + child_exiting_tree.connect(_handle_child_exiting) + +func _handle_child_entered(child: Node) -> void: + if child is TwitchAuth: auth = child + if child is TwitchAPI: api = child + +func _handle_child_exiting(child: Node) -> void: + if child is TwitchAuth: auth = null + if child is TwitchAPI: api = null + +func _ensure_nodes() -> void: + if (api == null): + api = TwitchAPI.new() + api.name = "ChatbotAPI" + add_child(api) + api.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if (auth == null): + auth = TwitchAuth.new() + auth.name = "ChatbotAuth" + add_child(auth) + auth.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + _ensure_nodes() + + api.token = token + api.oauth_setting = oauth_setting + auth.token = token + auth.oauth_setting = oauth_setting + auth.token_handler.token = token + auth.scopes = scopes + auth.token_handler.token_resolved.connect(_waiting_for_authentication.emit) + auth.token_handler.unauthenticated.connect(_waiting_for_authentication.emit) + +func authenticate() -> bool: + auth.token._load_tokens() + if auth.token.is_token_valid(): + return true + + if auth.token.has_refresh_token(): + auth.force_verify = false + auth.refresh_token() + await _waiting_for_authentication + if auth.token.is_token_valid(): + return true + else: + return false + + auth.force_verify = true + + return await auth.authorize() + +func _load_from_cache() -> TwitchUser: + var user: TwitchUser = TwitchUser.new() + if (_config_file.load(token._cache_path) == OK + and _config_file.has_section(token._identifier) + and _config_file.has_section_key(token._identifier, "bot_id")): + + user.id = _config_file.get_value(token._identifier, "bot_id", "") + user.display_name = _config_file.get_value(token._identifier, "bot_display", "") + user.login = _config_file.get_value(token._identifier, "bot_login", "") + return user + return null + +func _save_to_cache(user: TwitchUser) -> void: + if _config_file.load(token._cache_path) == OK and _config_file.has_section(token._identifier): + _config_file.set_value(token._identifier, "bot_id", user.id) + _config_file.set_value(token._identifier, "bot_display", user.display_name) + _config_file.set_value(token._identifier, "bot_login", user.login) + _config_file.save(token._cache_path) + +func get_user() -> TwitchUser: + var user: TwitchUser = _load_from_cache() + if not user: + if not auth.token.is_token_valid(): + push_error("Please authenticate first, before calling get_user()") + return null + var res: TwitchGetUsers.Response = await api.get_users(null) + if res.data.size() > 0: + user = res.data[0] + _save_to_cache(user) + else: + push_error("Failed to fetch token user information") + return null + + return user + +func invalidate_token() -> void: + auth.token_handler.revoke_token() + auth.token.invalidate() diff --git a/lib/twitcher_extended/chatbot_authorization.gd.uid b/lib/twitcher_extended/chatbot_authorization.gd.uid new file mode 100644 index 00000000..37469c4a --- /dev/null +++ b/lib/twitcher_extended/chatbot_authorization.gd.uid @@ -0,0 +1 @@ +uid://csq2hmf1bsgku diff --git a/lib/twitcher_extended/twitcher_extended.gd b/lib/twitcher_extended/twitcher_extended.gd new file mode 100644 index 00000000..afed2927 --- /dev/null +++ b/lib/twitcher_extended/twitcher_extended.gd @@ -0,0 +1,473 @@ +@tool +extends Twitcher +class_name TwitcherExtended + +#region Signals +signal _waiting_for_authentication +#endregion + +#region Constants +const POLL_TIMEOUT_MS: int = 30000 +#endregion + +#region Exports +@export_group("Twitcher Settings") +@export var oauth_settings: OAuthSetting = preload("res://addons/twitcher/twitch_oauth_setting.tres") +@export var scopes: OAuthScopes = preload("res://addons/twitcher/auth/preset_overlay_scopes.tres") +@export var streamer_token: OAuthToken +@export var chatbot_token: OAuthToken + +#region Node Exports +@export_subgroup("Twitcher Nodes") +@export var service: TwitchService +@export var chat: TwitchChat +@export var bot: TwitchBot +@export var chatbot_auth: ChatbotAuthorization +#endregion + +#region Twitcher Child Node Exports +@export_subgroup("Twitcher Child Nodes") +@export var eventsub: TwitchEventsub: + get(): + if service: + return service.eventsub + return null +@export var api: TwitchAPI: + get(): + if service: + return service.api + return null +@export var auth: TwitchAuth: + get(): + if service: + return service.auth + return null +@export var media: TwitchMediaLoader: + get(): + if service: + return service.media_loader + return null +#endregion +#endregion + +#region Public Variables +var streamer_user: TwitchUser +var bot_user: TwitchUser +#endregion + +#region Private Variables +var _cache_users: Dictionary[String, TwitchUser] = {} +var _log: TwitchLogger = TwitchLogger.new("TwitcherExtended") +var _commands: Dictionary[String, TwitchCommand] = {} +#endregion + +enum AuthStatus { + UNAUTHORIZED, + AUTHORIZED, + NEEDS_REFRESH +} + +#region Support Functions / Overriden Methods +func _init() -> void: + child_entered_tree.connect(_handle_child_entered) + child_exiting_tree.connect(_handle_child_exiting) + +func _ensure_nodes() -> void: + if not service: + # Ensure Service + service = TwitchService.new() + service.name = "Service" + add_child(service) + service.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + var tn: Twitcher = null + + # Ensure Auth + tn = TwitchAuth.new() + tn.name = "Auth" + service.add_child(tn) + tn.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + # Ensure API + tn = TwitchAPI.new() + tn.name = "API" + service.add_child(tn) + tn.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + # Ensure Eventsub + tn = TwitchEventsub.new() + tn.name = "Eventsub" + service.add_child(tn) + tn.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + # Ensure MediaLoader + tn = TwitchMediaLoader.new() + tn.name = "Media" + service.add_child(tn) + tn.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if not chat: + chat = TwitchChat.new() + chat.name = "Chat" + chat.subscribe_on_ready = false + add_child(chat) + chat.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if not bot: + bot = TwitchBot.new() + bot.name = "Bot" + add_child(bot) + bot.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + + if not chatbot_auth: + chatbot_auth = ChatbotAuthorization.new() + chatbot_auth.name = "ChatbotAuth" + add_child(chatbot_auth) + chatbot_auth.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else owner + +func _check_nodes() -> bool: + if service == null: return false + if eventsub == null: return false + if api == null: return false + if auth == null: return false + if media == null: return false + return true +#endregion + +#region Event Handlers +func _handle_child_entered(child: Node) -> void: + if child is TwitchService: service = child + if child is TwitchChat: chat = child + if child is TwitchBot: bot = child + if child is ChatbotAuthorization: chatbot_auth = child + +func _handle_child_exiting(child: Node) -> void: + if child is TwitchService and child == service: service = null + if child is TwitchChat and child == chat: chat = null + if child is TwitchBot and child == bot: bot = null + if child is ChatbotAuthorization and child == chatbot_auth: chatbot_auth = null +#endregion + +#region Godot Overrides +func _ready() -> void: + _log.d("is ready") + _ensure_nodes() + # Ensure Settings are properly setup + service.oauth_setting = oauth_settings + service.scopes = scopes + service.token = streamer_token + chat.api = service.api + chat.eventsub = service.eventsub + chat.media_loader = service.media_loader + bot.oauth_setting = oauth_settings + chatbot_auth.oauth_setting = oauth_settings + chatbot_auth.token = chatbot_token + + auth.token_handler.unauthenticated.connect(_waiting_for_authentication.emit) + auth.token_handler.token_resolved.connect(_waiting_for_authentication.emit) +#endregion + +#region Streamer Private Functions +func _twitcher_setup() -> void: + _log.d("Setting up nodes...") + service.propagate_call(&"do_setup") + for child in service.get_children(): + if child.has_method(&"wait_setup"): + await child.wait_setup() +#endregion + +#region Streamer Public Functions +func load_streamer_token() -> AuthStatus: + _log.d("Loading streamer tokens...") + var res = streamer_token._load_tokens() + if not res: + _log.d("Token doesn't exist") + return AuthStatus.UNAUTHORIZED + if streamer_token.is_token_valid(): + _log.d("Token authorized") + return AuthStatus.AUTHORIZED + if streamer_token.has_refresh_token(): + _log.d("Token needs refreshed") + return AuthStatus.NEEDS_REFRESH + _log.d("Token invalid") + return AuthStatus.UNAUTHORIZED + +func is_streamer_authed() -> bool: + return auth.is_authenticated + +func is_streamer_token_valid() -> bool: + return streamer_token.is_token_valid() + +func authorize_streamer() -> bool: + return await auth.authorize() + +func setup_streamer() -> bool: + _log.d("Setup streamer") + load_streamer_token() + if is_streamer_authed() and is_streamer_token_valid(): + _log.d("Saved tokens validated.") + _twitcher_setup() + streamer_user = await service.get_current_user() + chat.broadcaster_user = streamer_user + bot.receiver = streamer_user + chat.subscribe() + return true + + var res: bool = false + + if streamer_token.has_refresh_token(): + _log.d("Refreshing tokens...") + auth.refresh_token() + await _waiting_for_authentication + res = is_streamer_token_valid() + else: + _log.d("Acquiring token...") + res = await auth.authorize() + + if res: + _log.d("Tokens validated.") + _twitcher_setup() + streamer_user = await service.get_current_user() + chat.broadcaster_user = streamer_user + chat.sender_user = streamer_user + bot.receiver = streamer_user + chat.subscribe() + + return res +#endregion + +#region Chatbot Functions +func load_chatbot_token() -> AuthStatus: + _log.d("Loading chatbot tokens...") + var res = chatbot_token._load_tokens() + if not res: + _log.d("Token doesn't exist") + return AuthStatus.UNAUTHORIZED + if chatbot_token.is_token_valid(): + _log.d("Token authroized") + return AuthStatus.AUTHORIZED + if chatbot_token.has_refresh_token(): + _log.d("Token needs refreshed") + return AuthStatus.NEEDS_REFRESH + _log.d("Token invalid") + return AuthStatus.UNAUTHORIZED + +func is_chatbot_authed() -> bool: + return auth.is_authenticated + +func is_chatbot_token_valid() -> bool: + return streamer_token.is_token_valid() + +func authorize_chatbot() -> bool: + return await chatbot_auth.authenticate() + +func setup_chatbot() -> bool: + _log.d("Setup chatbot") + load_chatbot_token() + var res: bool = await chatbot_auth.authenticate() + if res: + _log.d("Chatbot authorized.") + bot_user = await chatbot_auth.get_user() + bot.sender = bot_user + return res +#endregion + +#region Public API shared between both Streamer and Chatbot as needed. +func send_message(message: String, as_streamer: bool = false) -> void: + if as_streamer: + await chat.send_message(message) + else: + await bot.send_message(message) + +func reply_message(message: String, msg_id: String, as_streamer: bool = false) -> void: + if as_streamer: + await chat.send_message(message, msg_id) + else: + await bot.send_message(message, msg_id) + +func get_users_by_id(...user_ids: Array) -> Array[TwitchUser]: + var tusers: Array[TwitchUser] = [] + var qusers: Array[String] = [] + for user_id in user_ids: + if _cache_users.has(user_id): + tusers.append(_cache_users[user_id]) + else: + qusers.append(user_id) + + if not qusers.is_empty(): + var opt = TwitchGetUsers.Opt.new() + opt.id = qusers + var tgur: TwitchGetUsers.Response = await api.get_users(opt) + if tgur.data.is_empty(): + return tusers + for tgu in tgur.data: + _cache_users[tgu.id] = tgu + _cache_users[tgu.login] = tgu + tusers.append(tgu) + + return tusers + +func get_users(...usernames: Array) -> Array[TwitchUser]: + var tusers: Array[TwitchUser] = [] + var qusers: Array[String] = [] + + for i in usernames.size(): + usernames[i] = usernames[i].trim_prefix("@") + + for username in usernames: + if _cache_users.has(username): + tusers.append(_cache_users[username]) + else: + qusers.append(username) + + if not qusers.is_empty(): + var opt = TwitchGetUsers.Opt.new() + opt.login = qusers + var tgur: TwitchGetUsers.Response = await api.get_users(opt) + if tgur.data.is_empty(): + return tusers + for tgu in tgur.data: + _cache_users[tgu.id] = tgu + _cache_users[tgu.login] = tgu + tusers.append(tgu) + + return tusers + +func get_user_by_id(user_id: String) -> TwitchUser: + if _cache_users.has(user_id): return _cache_users[user_id] + var user: TwitchUser = await service.get_user_by_id(user_id) + _cache_users[user_id] = user + _cache_users[user.login] = user + return user + +func get_user(username: String) -> TwitchUser: + username = username.trim_prefix("@") + if _cache_users.has(username): return _cache_users[username] + var user: TwitchUser = await service.get_user(username) + _cache_users[user.id] = user + _cache_users[username] = user + return user + +func subscribe_event(definition: TwitchEventsubDefinition, conditions: Dictionary) -> TwitchEventsubConfig: + if definition == null: + _log.e("TwitchEventsubDefinition is null") + return + + var config = TwitchEventsubConfig.create(definition, conditions) + eventsub.subscribe(config) + return config + +func wait_for_eventsub_connection() -> void: + if eventsub == null: + _log.e("TwitchEventsub Node is missing") + return + await eventsub.wait_for_connection() + +func get_subscriptions() -> Array[TwitchEventsubConfig]: + if eventsub == null: + _log.e("TwitchEventsub Node is missing") + return [] + return eventsub.get_subscriptions() + +func shoutout(user: TwitchUser, broadcaster: TwitchUser = null, moderator: TwitchUser = null) -> void: + if not streamer_user: + return + + if not broadcaster: + broadcaster = streamer_user + if not moderator: + moderator = bot_user + + api.send_a_shoutout(broadcaster.id, moderator.id, user.id) + +func announcement(message: String, color: TwitchAnnouncementColor = TwitchAnnouncementColor.PRIMARY, as_broadcaster: bool = true) -> void: + if not streamer_user: + return + var moderator: TwitchUser = streamer_user if as_broadcaster else bot_user + + var body = TwitchSendChatAnnouncement.Body.new() + body.message = message + body.color = color.value + api.send_chat_announcement(body, moderator.id, streamer_user.id) + +func add_command(command: String, callback: Callable, args_min: int = 0, args_max: int = -1, + permission_level: TwitchCommand.PermissionFlag = TwitchCommand.PermissionFlag.EVERYONE, + where: TwitchCommand.WhereFlag = TwitchCommand.WhereFlag.CHAT, user_cooldown: float = 0, + global_cooldown: float = 0) -> TwitchCommand: + var command_node = TwitchCommand.new() + command_node.command = command + command_node.command_received.connect(callback) + command_node.args_min = args_min + command_node.args_max = args_max + command_node.permission_level = permission_level + command_node.where = where + command_node.user_cooldown = user_cooldown + command_node.global_cooldown = global_cooldown + add_child(command_node) + _commands[command] = command_node + _log.i("Register command %s" % command) + return command_node + +func remove_command(command: String) -> void: + _log.i("Remove command %s" % command) + var command_node: TwitchCommand = _commands.get(command, null) + if command_node: + command_node.queue_free() + _commands.erase(command) + +func whisper(_message: String, _username: String, _as_streamer: bool = false) -> void: + _log.e("Whisper from bots aren't supported by Twitch anymore. See https://dev.twitch.tv/docs/irc/chat-commands/ at /w") + +func get_emotes_data(channel_id: String = "global") -> Dictionary: + return await media.get_cached_emotes(channel_id) + +func get_badges_data(channel_id: String = "global") -> Dictionary: + return await media.get_cached_badges(channel_id) + +func get_emotes(ids: Array[String]) -> Dictionary[String, SpriteFrames]: + return await media.get_emotes(ids) + +func get_emotes_by_definition(emotes: Array[TwitchEmoteDefinition]) -> Dictionary[TwitchEmoteDefinition, SpriteFrames]: + return await media.get_emotes_by_definition(emotes) + +func poll(title: String, choices: Array[String], duration: int = 60, channel_points_voting_enabled: bool = false, channel_points_per_vote: int = 1000) -> Dictionary: + var body_choices: Array[TwitchCreatePoll.BodyChoices] = [] + for choice: String in choices: + var body_choice = TwitchCreatePoll.BodyChoices.create(choice) + body_choices.append(body_choice) + duration = clamp(duration, 15, 1800) + var poll_body: TwitchCreatePoll.Body = TwitchCreatePoll.Body.create(streamer_user.id, title, body_choices, duration) + if channel_points_voting_enabled: + poll_body.channel_points_per_vote = channel_points_per_vote + poll_body.channel_points_voiting_enabled = channel_points_voting_enabled + var poll_response: TwitchCreatePoll.Response = await api.create_poll(poll_body) + if poll_response.response.response_code != 200: + var error_message: String = poll_response.response.response_data.get_string_from_utf8() + push_error("Can't create poll response cause of ", error_message) + return {} + var poll: TwitchPoll = poll_response.data[0] + var poll_end_time: int = Time.get_ticks_msec() + duration * 100 * POLL_TIMEOUT_MS + var event: TwitchEventsub.Event + if eventsub && eventsub.has_subscription(TwitchEventsubDefinition.CHANNEL_POLL_END, {&"broadcaster_user_id": streamer_user.id}): + var poll_ended: bool + while not poll_ended: + if poll_end_time < Time.get_ticks_msec(): + return {} + event = await eventsub.event_received + if event.Type != TwitchEventsubDefinition.CHANNEL_POLL_END: continue + if event.data[&"id"] != poll.id: continue + break + else: + _log.i("Can't wait for poll end. Either eventsub is not set to it not listening to ending polls") + return {} + return event.data + +func get_cheermote_data() -> Array[TwitchCheermote]: + if media == null: + _log.e("TwitchMediaLoader was not set within %s" % get_tree_string()) + return [] + await media.preload_cheemote() + return media.all_cheermotes() + +func get_cheermote(definition: TwitchCheermoteDefinition) -> Dictionary: + return await media.get_cheermotes(definition) +#endregion diff --git a/lib/twitcher_extended/twitcher_extended.gd.uid b/lib/twitcher_extended/twitcher_extended.gd.uid new file mode 100644 index 00000000..15da7774 --- /dev/null +++ b/lib/twitcher_extended/twitcher_extended.gd.uid @@ -0,0 +1 @@ +uid://d003jb645nrji diff --git a/main_win.gd b/main_win.gd new file mode 100644 index 00000000..ee404b39 --- /dev/null +++ b/main_win.gd @@ -0,0 +1,128 @@ +extends Control + +@onready var controls: VBoxContainer = %Controls +@onready var auth_twitch: Button = %AuthTwitch +@onready var auth_bot: Button = %AuthBot +@onready var bot_hello: Button = %BotHello +@onready var chat_history: ScrollContainer = %ChatHistory +@onready var chat_list: VBoxContainer = %ChatList +@onready var auto_connect: CheckBox = %AutoConnect +@onready var exit_overlay: Button = %ExitOverlay + + +#region Twitch Nodes +@onready var twitcher: TwitcherExtended = $TwitcherExtended +#endregion + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + Globals.twitcher = twitcher + auto_connect.button_pressed = Globals.settings.auto_connect + auth_twitch.pressed.connect(_handle_twitch_auth) + auth_bot.pressed.connect(_handle_bot_auth) + bot_hello.pressed.connect(_handle_bot_hello) + twitcher.chat.message_received.connect(_handle_chat_message) + get_window().mouse_entered.connect(func(): controls.visible = true) + get_window().mouse_exited.connect(func(): controls.visible = false) + exit_overlay.pressed.connect(func(): get_tree().quit()) + auto_connect.pressed.connect(func(): Globals.settings.auto_connect = auto_connect.button_pressed) + + var res := twitcher.load_streamer_token() + if res == TwitcherExtended.AuthStatus.UNAUTHORIZED: + return + + if auto_connect.button_pressed: + _handle_twitch_auth() + res = twitcher.load_chatbot_token() + if res == TwitcherExtended.AuthStatus.UNAUTHORIZED: + return + _handle_bot_auth() + +func _handle_twitch_auth() -> void: + await twitcher.setup_streamer() + if twitcher.is_streamer_authed(): + auth_twitch.disabled = true + +func _handle_bot_auth() -> void: + await twitcher.setup_chatbot() + if twitcher.is_chatbot_authed(): + auth_bot.disabled = true + +func _handle_bot_hello() -> void: + twitcher.send_message("Hello") + +func _handle_chat_message(message: TwitchChatMessage) -> void: + var badges_dict: Dictionary = await message.get_badges(twitcher.media) + var badges: Array[SpriteFrames] = [] + badges.assign(badges_dict.values()) + + var result_message: String = "" + var badge_id: int = 0 + for badge: SpriteFrames in badges: + result_message += "[sprite id='0-%s']%s[/sprite]" % [badge_id, badge.resource_path] + badge_id += 1 + result_message += "[color=%s]%s[/color]: " % [message.get_color(), message.chatter_user_name] + + match message.message_type: + # The default message style + TwitchChatMessage.MessageType.text: + result_message = await show_text(message, result_message) + + # When someone is using the gigantified emotes + TwitchChatMessage.MessageType.power_ups_gigantified_emote: + result_message = await show_text(message, result_message, 3) + + # When someone is using the hilight my message from channel points rewards + TwitchChatMessage.MessageType.channel_points_highlighted: + result_message += "[bgcolor=#755ebc][color=#e9fffb]" + result_message = await show_text(message, result_message) + result_message += "[/color][/bgcolor]" + + # When someone is using teh message effect bit reward: + TwitchChatMessage.MessageType.power_ups_message_effect: + result_message += "[shake rate=20.0 level=5 connected=1]" + result_message = await show_text(message, result_message) + result_message += "[/shake]" + + append_history(result_message) + +func append_history(message: String) -> void: + var msg: RichTextLabel = RichTextLabel.new() + msg.bbcode_enabled = true + msg.fit_content = true + + var effect: SpriteFrameEffect = SpriteFrameEffect.new() + msg.install_effect(effect) + chat_list.add_child(msg) + + message = effect.prepare_message(message, msg) + msg.text = "%s %s" % [_get_time(), message] + + await get_tree().process_frame + var rect := msg.get_rect() + chat_history.scroll_vertical = roundi(rect.position.y + rect.size.y) + +func _get_time() -> String: + var time_data: Dictionary = Time.get_time_dict_from_system() + return "%02d:%02d" % [time_data["hour"], time_data["minute"]] + + +func show_text(message: TwitchChatMessage, current_text: String, emote_scale: int = 1) -> String: + await message.load_emotes_from_fragment(twitcher.media) + + var fragment_id: int = 0 + for fragment: TwitchChatMessage.Fragment in message.message.fragments: + fragment_id += 1 + match fragment.type: + TwitchChatMessage.FragmentType.text: + current_text += fragment.text + TwitchChatMessage.FragmentType.cheermote: + var cheermote_scale: StringName = TwitchCheermoteDefinition.SCALE_MAP.get(emote_scale, TwitchCheermoteDefinition.SCALE_1) + var cheermote: SpriteFrames = await fragment.cheermote.get_sprite_frames(twitcher.media, cheermote_scale) + current_text += "[sprite id='f-%s']%s[/sprite]" % [fragment_id, cheermote.resource_path] + TwitchChatMessage.FragmentType.emote: + var emote: SpriteFrames = await fragment.emote.get_sprite_frames(twitcher.media, emote_scale) + current_text += "[sprite id='f-%s']%s[/sprite]" % [fragment_id, emote.resource_path] + TwitchChatMessage.FragmentType.mention: + current_text += "[color=%s]%s[/color]" % ["#00a0b6", fragment.mention.user_name] + return current_text diff --git a/main_win.gd.uid b/main_win.gd.uid new file mode 100644 index 00000000..ea3825c0 --- /dev/null +++ b/main_win.gd.uid @@ -0,0 +1 @@ +uid://be2000gog56qw diff --git a/main_win.tscn b/main_win.tscn new file mode 100644 index 00000000..300e03a0 --- /dev/null +++ b/main_win.tscn @@ -0,0 +1,226 @@ +[gd_scene format=3 uid="uid://2t2hslaaewee"] + +[ext_resource type="Script" uid="uid://be2000gog56qw" path="res://main_win.gd" id="1_bgu0c"] +[ext_resource type="Script" uid="uid://d003jb645nrji" path="res://lib/twitcher_extended/twitcher_extended.gd" id="2_x1kh3"] +[ext_resource type="Resource" uid="uid://dsde4iau3rlaw" path="res://tokens/streamer_token.tres" id="3_vhvw8"] +[ext_resource type="Resource" uid="uid://jqo15d6dhmkq" path="res://tokens/chatbot_token.tres" id="4_oe8se"] +[ext_resource type="Script" uid="uid://i8st3lv0lidh" path="res://addons/twitcher/twitch_service.gd" id="5_ma7y3"] +[ext_resource type="Resource" uid="uid://ry64ckbckh6m" path="res://addons/twitcher/twitch_oauth_setting.tres" id="6_u8aqo"] +[ext_resource type="Resource" uid="uid://fcmfkstye4bq" path="res://addons/twitcher/auth/preset_overlay_scopes.tres" id="7_0gi5c"] +[ext_resource type="Script" uid="uid://dcq1bvfrqimqq" path="res://addons/twitcher/chat/twitch_chat.gd" id="8_flv1a"] +[ext_resource type="Script" uid="uid://bf3mvh373suhc" path="res://addons/twitcher/chat/twitch_bot.gd" id="9_fuqxn"] +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="10_5puf8"] +[ext_resource type="Script" uid="uid://blmhj3j00yk45" path="res://addons/twitcher/eventsub/twitch_eventsub.gd" id="11_1ed2e"] +[ext_resource type="Script" uid="uid://b52xp7c23ucfk" path="res://addons/twitcher/lib/oOuch/oauth_token.gd" id="11_o2vln"] +[ext_resource type="Script" uid="uid://cw30cwveway65" path="res://addons/twitcher/generated/twitch_api.gd" id="12_1ed2e"] +[ext_resource type="Script" uid="uid://d4lyup0vy1wtu" path="res://addons/twitcher/media/twitch_media_loader.gd" id="12_7nx26"] +[ext_resource type="Script" uid="uid://00xbijwpi8xa" path="res://addons/twitcher/lib/oOuch/oauth_setting.gd" id="13_7nx26"] +[ext_resource type="Texture2D" uid="uid://g1dbcjksbotw" path="res://addons/twitcher/assets/fallback_texture.tres" id="13_l81bt"] +[ext_resource type="Script" uid="uid://6v8jnfjwbnhm" path="res://addons/twitcher/media/twitch_image_transformer.gd" id="14_dr7ot"] +[ext_resource type="Script" uid="uid://iv0mgv0lu8b0" path="res://addons/twitcher/auth/twitch_auth.gd" id="14_l81bt"] +[ext_resource type="Resource" uid="uid://b8735fmqjt8up" path="res://addons/twitcher/chat/twitch_bot_scopes.tres" id="15_dr7ot"] +[ext_resource type="Script" uid="uid://bf0wi70haua35" path="res://addons/twitcher/lib/oOuch/oauth.gd" id="16_m7rpr"] +[ext_resource type="Script" uid="uid://blnbogtrshw4r" path="res://addons/twitcher/auth/twitch_token_handler.gd" id="17_lpeoe"] +[ext_resource type="Script" uid="uid://csq2hmf1bsgku" path="res://lib/twitcher_extended/chatbot_authorization.gd" id="18_vifpv"] +[ext_resource type="PackedScene" uid="uid://djdxpwrmlrsp8" path="res://ChatAvatars/CatSlimes/cat_world.tscn" id="23_7nx26"] +[ext_resource type="Script" uid="uid://cv06lthuq2c2o" path="res://general_commands.gd" id="24_dr7ot"] +[ext_resource type="Script" uid="uid://bmluckfvgm1c2" path="res://addons/twitcher/chat/twitch_command.gd" id="24_l81bt"] + +[sub_resource type="Resource" id="Resource_m7rpr"] +script = ExtResource("14_dr7ot") + +[sub_resource type="Resource" id="Resource_lpeoe"] +script = ExtResource("11_o2vln") +_identifier = "Bot-Token" + +[sub_resource type="Resource" id="Resource_dr7ot"] +script = ExtResource("13_7nx26") +token_url = "https://id.twitch.tv/oauth2/token" +authorization_url = "https://id.twitch.tv/oauth2/authorize" +device_authorization_url = "https://id.twitch.tv/oauth2/device" +cache_file = "user://auth.conf" +client_id = "tlbzmtdy2itfeazyuumchynsez6pqv" +authorization_flow = 3 +client_secret = "I+Psn7ZIjTlehNFSoJTbk0sLL9xWqKDelHLDMcvYZwQ=" + +[node name="MainWin" type="Control" unique_id=395734301] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_bgu0c") + +[node name="Controls" type="VBoxContainer" parent="." unique_id=1372792566] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="Controls" unique_id=1218191491] +layout_mode = 2 + +[node name="AuthTwitch" type="Button" parent="Controls/HBoxContainer" unique_id=262058607] +unique_name_in_owner = true +layout_mode = 2 +text = "Auth Twitch" + +[node name="AuthBot" type="Button" parent="Controls/HBoxContainer" unique_id=64725557] +unique_name_in_owner = true +layout_mode = 2 +text = "Auth Bot" + +[node name="BotHello" type="Button" parent="Controls/HBoxContainer" unique_id=353165911] +unique_name_in_owner = true +layout_mode = 2 +text = "Bot Send Hello" + +[node name="AutoConnect" type="CheckBox" parent="Controls/HBoxContainer" unique_id=171499895] +unique_name_in_owner = true +layout_mode = 2 +text = "Auto-Connect" + +[node name="ExitOverlay" type="Button" parent="Controls/HBoxContainer" unique_id=655410314] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 10 +text = "Exit" + +[node name="ChatHistory" type="ScrollContainer" parent="Controls" unique_id=1443037902] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="ChatList" type="VBoxContainer" parent="Controls/ChatHistory" unique_id=895834768] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="TwitcherExtended" type="Node" parent="." unique_id=1225944144 node_paths=PackedStringArray("service", "chat", "bot", "chatbot_auth", "eventsub", "api", "auth", "media")] +script = ExtResource("2_x1kh3") +streamer_token = ExtResource("3_vhvw8") +chatbot_token = ExtResource("4_oe8se") +service = NodePath("Service") +chat = NodePath("Chat") +bot = NodePath("Bot") +chatbot_auth = NodePath("ChatbotAuth") +eventsub = NodePath("Service/Eventsub") +api = NodePath("Service/API") +auth = NodePath("Service/Auth") +media = NodePath("Service/Media") +metadata/_custom_type_script = "uid://d003jb645nrji" + +[node name="Service" type="Node" parent="TwitcherExtended" unique_id=1349639042] +script = ExtResource("5_ma7y3") +oauth_setting = ExtResource("6_u8aqo") +scopes = ExtResource("7_0gi5c") +token = ExtResource("3_vhvw8") + +[node name="Auth" type="Node" parent="TwitcherExtended/Service" unique_id=1657722616] +script = ExtResource("14_l81bt") +oauth_setting = ExtResource("6_u8aqo") +token = ExtResource("3_vhvw8") +scopes = ExtResource("7_0gi5c") + +[node name="OAuth" type="Node" parent="TwitcherExtended/Service/Auth" unique_id=1285979164 node_paths=PackedStringArray("token_handler")] +script = ExtResource("16_m7rpr") +oauth_setting = ExtResource("6_u8aqo") +scopes = ExtResource("7_0gi5c") +token_handler = NodePath("../TokenHandler") + +[node name="TokenHandler" type="Node" parent="TwitcherExtended/Service/Auth" unique_id=176014831] +script = ExtResource("17_lpeoe") +oauth_setting = ExtResource("6_u8aqo") +token = ExtResource("3_vhvw8") + +[node name="API" type="Node" parent="TwitcherExtended/Service" unique_id=1862110264] +script = ExtResource("12_1ed2e") +token = ExtResource("3_vhvw8") +oauth_setting = ExtResource("6_u8aqo") + +[node name="Eventsub" type="Node" parent="TwitcherExtended/Service" unique_id=1013516914 node_paths=PackedStringArray("api")] +script = ExtResource("11_1ed2e") +api = NodePath("../API") +scopes = ExtResource("7_0gi5c") + +[node name="Media" type="Node" parent="TwitcherExtended/Service" unique_id=1023021895 node_paths=PackedStringArray("api")] +script = ExtResource("12_7nx26") +api = NodePath("../API") +image_transformer = SubResource("Resource_m7rpr") + +[node name="Chat" type="Node" parent="TwitcherExtended" unique_id=1372727250 node_paths=PackedStringArray("media_loader", "eventsub", "api")] +script = ExtResource("8_flv1a") +media_loader = NodePath("../Service/Media") +eventsub = NodePath("../Service/Eventsub") +api = NodePath("../Service/API") +subscribe_on_ready = false + +[node name="Bot" type="Node" parent="TwitcherExtended" unique_id=1052569275] +script = ExtResource("9_fuqxn") +oauth_setting = ExtResource("6_u8aqo") +bot_token = SubResource("Resource_lpeoe") + +[node name="BotApi" type="Node" parent="TwitcherExtended/Bot" unique_id=357061728] +script = ExtResource("12_1ed2e") +token = SubResource("Resource_lpeoe") +oauth_setting = SubResource("Resource_dr7ot") + +[node name="BotAuth" type="Node" parent="TwitcherExtended/Bot" unique_id=1765548387] +script = ExtResource("14_l81bt") +oauth_setting = SubResource("Resource_dr7ot") +token = SubResource("Resource_lpeoe") +scopes = ExtResource("15_dr7ot") + +[node name="OAuth" type="Node" parent="TwitcherExtended/Bot/BotAuth" unique_id=753392258 node_paths=PackedStringArray("token_handler")] +script = ExtResource("16_m7rpr") +oauth_setting = SubResource("Resource_dr7ot") +scopes = ExtResource("15_dr7ot") +token_handler = NodePath("../TokenHandler") + +[node name="TokenHandler" type="Node" parent="TwitcherExtended/Bot/BotAuth" unique_id=1838561754] +script = ExtResource("17_lpeoe") +oauth_setting = SubResource("Resource_dr7ot") +token = SubResource("Resource_lpeoe") + +[node name="ChatbotAuth" type="Node" parent="TwitcherExtended" unique_id=1435619386] +script = ExtResource("18_vifpv") +token = ExtResource("4_oe8se") +oauth_setting = ExtResource("6_u8aqo") + +[node name="ChatbotAPI" type="Node" parent="TwitcherExtended/ChatbotAuth" unique_id=1515744664] +script = ExtResource("12_1ed2e") +token = ExtResource("4_oe8se") +oauth_setting = ExtResource("6_u8aqo") + +[node name="ChatbotAuth" type="Node" parent="TwitcherExtended/ChatbotAuth" unique_id=773762330] +script = ExtResource("14_l81bt") +oauth_setting = ExtResource("6_u8aqo") +token = ExtResource("4_oe8se") +scopes = ExtResource("15_dr7ot") + +[node name="OAuth" type="Node" parent="TwitcherExtended/ChatbotAuth/ChatbotAuth" unique_id=1565838954 node_paths=PackedStringArray("token_handler")] +script = ExtResource("16_m7rpr") +oauth_setting = ExtResource("6_u8aqo") +scopes = ExtResource("15_dr7ot") +token_handler = NodePath("../TokenHandler") + +[node name="TokenHandler" type="Node" parent="TwitcherExtended/ChatbotAuth/ChatbotAuth" unique_id=1460100941] +script = ExtResource("17_lpeoe") +oauth_setting = ExtResource("6_u8aqo") +token = ExtResource("4_oe8se") + +[node name="CatWorld" parent="." unique_id=1301842669 instance=ExtResource("23_7nx26")] + +[node name="GeneralCommands" type="Node" parent="." unique_id=1080713698] +script = ExtResource("24_dr7ot") + +[node name="Test" type="Node" parent="GeneralCommands" unique_id=1399617447] +script = ExtResource("24_l81bt") +command = "test" +description = "Test command to see if everythingis working." +metadata/_custom_type_script = "uid://bmluckfvgm1c2" diff --git a/project.godot b/project.godot new file mode 100644 index 00000000..9852be2b --- /dev/null +++ b/project.godot @@ -0,0 +1,64 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="StreamOverlay" +config/description="Enter an interesting project description here!" +run/main_scene="uid://2t2hslaaewee" +config/features=PackedStringArray("4.6") +config/icon="res://icon.png" + +[autoload] + +Globals="*uid://cuap0k5jagdtj" +ChatManager="*uid://ct1s5eymb8mns" + +[display] + +window/size/transparent=true +window/per_pixel_transparency/allowed=true + +[editor_plugins] + +enabled=PackedStringArray("res://addons/gdata_orm/plugin.cfg", "res://addons/gde_gozen/plugin.cfg", "res://addons/kenny_spritesheet_importer/plugin.cfg", "res://addons/twitcher/plugin.cfg") + +[gui] + +theme/custom="uid://u5qh1r677ec2" + +[input] + +spawn_avatar={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} + +[rendering] + +textures/canvas_textures/default_texture_filter=0 +viewport/transparent_background=true + +[twitcher] + +editor/game_oauth_token="res://addons/twitcher/default_oauth_token.tres" +editor/game_oauth_setting="res://addons/twitcher/twitch_oauth_setting.tres" +editor/project_preset=&"Other" +editor/show_setup_on_startup=false +logs/TwitchAuth="debug" +logs/TwitchEventsubConfig="debug" +logs/TwitchAPI="debug" +logs/TwitchEventsub="debug" +logs/TwitchChat="debug" +logs/TwitchBot="debug" +logs/TwitcherExtended="debug" +logs/CatWorldAvatars="debug" +logs/ChatManager="debug" diff --git a/tokens/chatbot_token.tres b/tokens/chatbot_token.tres new file mode 100644 index 00000000..7abdbfcf --- /dev/null +++ b/tokens/chatbot_token.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="OAuthToken" format=3 uid="uid://jqo15d6dhmkq"] + +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="1_0qqcp"] +[ext_resource type="Script" uid="uid://b52xp7c23ucfk" path="res://addons/twitcher/lib/oOuch/oauth_token.gd" id="2_70p3b"] + +[resource] +script = ExtResource("2_70p3b") +_identifier = "TempBotToken" +metadata/_custom_type_script = "uid://b52xp7c23ucfk" diff --git a/tokens/streamer_token.tres b/tokens/streamer_token.tres new file mode 100644 index 00000000..9094bb6a --- /dev/null +++ b/tokens/streamer_token.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="OAuthToken" format=3 uid="uid://dsde4iau3rlaw"] + +[ext_resource type="Resource" uid="uid://c4scwuk8q0r40" path="res://addons/twitcher/lib/oOuch/default_key_provider.tres" id="1_a3g4w"] +[ext_resource type="Script" uid="uid://b52xp7c23ucfk" path="res://addons/twitcher/lib/oOuch/oauth_token.gd" id="2_l5hcj"] + +[resource] +script = ExtResource("2_l5hcj") +_identifier = "StreamerAuth" +metadata/_custom_type_script = "uid://b52xp7c23ucfk" diff --git a/tools/create_skins.gd b/tools/create_skins.gd new file mode 100644 index 00000000..07c38344 --- /dev/null +++ b/tools/create_skins.gd @@ -0,0 +1,32 @@ +@tool +extends EditorScript + +var template: SpriteFrames = load("res://ChatAvatars/CatSlimes/SpriteFrames/white.tres") +const assets_path: String = "res://ChatAvatars/CatSlimes/Assets/" +const skins_path: String = "res://ChatAvatars/CatSlimes/SpriteFrames/" + +# Called when the script is executed (using File -> Run in Script Editor). +func _run() -> void: + var folders = DirAccess.get_directories_at(assets_path) + for folder in folders: + var skin := folder.to_snake_case() + if skin == "white": continue + print("Generating Skin %s..." % [skin]) + var anims = template.get_animation_names() + var sf: SpriteFrames = SpriteFrames.new() + for anim in anims: + sf.add_animation(anim) + var anim_file_name = anim.capitalize() + anim_file_name = "".join(anim_file_name.split(" ")) + var anim_file := assets_path.path_join(folder).path_join(anim_file_name) + ".png" + var img := load(anim_file) + for i in template.get_frame_count(anim): + var txtr: AtlasTexture = template.get_frame_texture(anim, i) + var frame: AtlasTexture = AtlasTexture.new() + frame.atlas = img + frame.region = txtr.region + sf.add_frame(anim, frame) + sf.remove_animation(&"default") + ResourceSaver.save(sf, skins_path.path_join(skin) + ".tres") + print("Saved to: %s" % [skins_path.path_join(skin) + ".tres"]) + print ("Done.") diff --git a/tools/create_skins.gd.uid b/tools/create_skins.gd.uid new file mode 100644 index 00000000..755f56ec --- /dev/null +++ b/tools/create_skins.gd.uid @@ -0,0 +1 @@ +uid://da4jhwvbt1r5s diff --git a/tools/typed_object_set.gd b/tools/typed_object_set.gd new file mode 100644 index 00000000..58ed4556 --- /dev/null +++ b/tools/typed_object_set.gd @@ -0,0 +1,23 @@ +@tool +extends EditorScript + +class Test: + var my_typed_dict: Dictionary[String, int] = {} + var my_typed_array: Array[int] + +# Called when the script is executed (using File -> Run in Script Editor). +func _run() -> void: + var obj: Test = Test.new() + var dict_data = "{\"test\": 1, \"alpha\": 2}" + var array_data = "[13,12,5]" + print("Normal Object.set()") + obj.set("my_typed_dict", JSON.parse_string(dict_data)) + obj.set("my_typed_array", JSON.parse_string(array_data)) + print("Typed Dictionary: ", obj.my_typed_dict) + print("Typed Array: ", obj.my_typed_array) + + print("Typed Object.get().assign()") + obj.get("my_typed_dict").assign(JSON.parse_string(dict_data)) + obj.get("my_typed_array").assign(JSON.parse_string(array_data)) + print("Typed Dictionary: ", obj.my_typed_dict) + print("Typed Array: ", obj.my_typed_array) diff --git a/tools/typed_object_set.gd.uid b/tools/typed_object_set.gd.uid new file mode 100644 index 00000000..f7bad559 --- /dev/null +++ b/tools/typed_object_set.gd.uid @@ -0,0 +1 @@ +uid://ccxhqc4cqxt7l