Skip to content

Commit f377aa1

Browse files
CubesterYTSharkPool-SPGarboMuffinDangoCatBrackets-Coder
authored
New extension: Webhooks (TurboHook replacement) (#1873)
Hello all! The time has finally come, the long awaited successor to the horrendous extension that is **"TurboHook"**. Under code name "TurboHook V2", this extension took time to develop as I waited for certain APIs to happen (extension storage my beloved), and also just forgetting about the extension in the first place 😅. I deemed it smart to make this a whole new extension entirely, and TurboHook will now be hidden. Video Example: https://github.com/user-attachments/assets/d43db41e-12f7-4794-94b3-78f34632e63c Honestly, I'm going to consider this extension my Magnum Opus at the moment, since this represents my growth since I first contributed here. The difference between TurboHook and this extension is insane. --------- Co-authored-by: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Co-authored-by: Muffin <muffin@muffin.ink> Co-authored-by: DangoCat[bot] <dangocat@users.noreply.github.com> Co-authored-by: Brackets-Coder <142950368+Brackets-Coder@users.noreply.github.com>
1 parent 9458ce1 commit f377aa1

4 files changed

Lines changed: 387 additions & 1 deletion

File tree

docs/CubesterYT/Webhooks.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Webhooks
2+
3+
This extension allows you to use [Webhooks](https://en.wikipedia.org/wiki/Webhook). It provides a simple way to save multiple Webhooks to your project for easy management and easy data handling.
4+
5+
These blocks are made with simplicity in mind so that your project isn't messy when you implement Webhooks.
6+
7+
## Adding a Webhook
8+
9+
When loading the extension, you are provided with a button, `Add a Webhook`, to make a new Webhook. It will prompt you to type in a name, which the Webhook registers under, and then prompt you to provide the URL of the Webhook. Please make sure the Webhook URL is valid before making the Webhook, else the extension can't send anything.
10+
11+
## Deleting a Webhook
12+
13+
You can delete a specific Webhook by pressing the `Delete a Webhook` button, where you will be prompted to enter the name of the Webhook you wish to delete.
14+
15+
## Blocks
16+
17+
```scratch
18+
(data of [Webhook v] :: #C73A63)
19+
```
20+
21+
This block reports the data the Webhook chosen from the dropdown is currently assigned to.
22+
23+
```scratch
24+
set data of [Webhook v] to [{"key":"value"}] and type to [application/json v] :: #C73A63
25+
```
26+
27+
This block is used to set data to the chosen Webhook from the list. It only supports JSON inputs, but using the second dropdown, you can make the content type either `application/json` or `application/x-www-form-urlencoded`. The extension will automatically handle conversions for your ease.
28+
29+
```scratch
30+
send data to [Webhook v] :: #C73A63
31+
```
32+
33+
This block sends the data that is set for the selected Webhook.

extensions/CubesterYT/Webhooks.js

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
// Name: Webhooks
2+
// ID: cubesterWebhooks
3+
// Description: A modern, very capable Webhook extension.
4+
// By: CubesterYT <https://scratch.mit.edu/users/CubesterYT/>
5+
// License: MPL-2.0
6+
7+
// Version V.1.0.0
8+
9+
(function (Scratch) {
10+
"use strict";
11+
12+
const icon =
13+
"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNjQiCiAgIGhlaWdodD0iNjQiCiAgIHZpZXdCb3g9IjAgMCAxNi45MzMzMzMgMTYuOTMzMzMzIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmcxIgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjQgKDg2YThhZDcsIDIwMjQtMTAtMTEpIgogICBzb2RpcG9kaTpkb2NuYW1lPSJXZWJob29rc0ljb24uc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MSIKICAgICBwYWdlY29sb3I9IiM1MDUwNTAiCiAgICAgYm9yZGVyY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIxIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iIzUwNTA1MCIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6em9vbT0iOC4xOCIKICAgICBpbmtzY2FwZTpjeD0iMzIuMjczODM5IgogICAgIGlua3NjYXBlOmN5PSIzMy4wNjg0NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjE5MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTAxNyIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTgiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9Ii04IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIiAvPjxkZWZzCiAgICAgaWQ9ImRlZnMxIiAvPjxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj48Y2lyY2xlCiAgICAgICBzdHlsZT0ib3BhY2l0eToxO2ZpbGw6I2M3M2E2MztzdHJva2Utd2lkdGg6MC4yNzA0NDc7ZmlsbC1vcGFjaXR5OjEiCiAgICAgICBpZD0icGF0aDIiCiAgICAgICBjeD0iOC40NjY2NjYyIgogICAgICAgY3k9IjguNDY2NjY2MiIKICAgICAgIHI9IjguNDY2NjY2MiIgLz48cGF0aAogICAgICAgZmlsbD0iIzAwMDAwMCIKICAgICAgIGQ9Ik0gOC4yMTUyMjc2LDIuNzMyNjkgSCA4LjIwMDg5MzkgTCA4LjA1NzU0NDYsMi43NjEzNTc0IFEgNy41ODQ0OTIzLDIuODE4NzAzMyA3LjEzMjk0MjcsMy4wMjY1NTQzIDYuNjgxMzkyMSwzLjIzNDQwOTggNi4zMzczNTMxLDMuNTc4NDQ4NyA1Ljc0OTYyMTEsNC4xMDg4NDE0IDUuNTIwMjYyOSw0Ljg4MjkyNzIgNS4zOTEyNDg0LDUuMzEyOTc1IDUuNDA1NTgzMyw1Ljc4NjAyNzQgNS40MTk5MTcsNi4yNTkwNzk3IDUuNTc3NjAyMiw2LjY4OTEyNzYgNS44MjEyOTYzLDcuMzM0MTk5OSA2LjMyMzAxODMsNy44MDcyNTIzIEwgNi41MDkzNzMxLDcuOTc5MjcxMiA1LjMxOTU3MzIsOS45MTQ0ODYxIFEgNC45NDY4NjU3LDkuOTAwMTUyMSA0LjY3NDUwMiwxMC4wMTQ4MzMgcSAtMC40MzAwNDc5LDAuMTg2MzU1IC0wLjY0NTA3MTMsMC41NzMzOTggLTAuMTAwMzQ3LDAuMjE1MDI0IC0wLjEyOTAxNDUsMC40NDQzODIgLTAuMDI4NjY3LDAuMjI5MzU5IDAuMDI4NjY4LDAuNDU4NzE4IDAuMTE0Njc5NiwwLjQzMDA0NyAwLjQ3MzA1MjMsMC42ODgwNzYgMC4yMTUwMjQ1LDAuMTQzMzUgMC40NzMwNTIzLDAuMjA3ODU3IDAuMjU4MDI5LDAuMDY0NTEgMC41MTYwNTc5LDAuMDI4NjcgMC4yNTgwMjksLTAuMDM1ODQgMC40OTQ1NTUxLC0wLjE3OTE4NSAwLjIzNjUyNjIsLTAuMTQzMzUgMC4zOTQyMTAzLC0wLjM1ODM3NCAwLjE3MjAxOSwtMC4yNDM2OTMgMC4yMDc4NTY2LC0wLjU1OTA2MSAwLjAzNTg0LC0wLjMxNTM3IC0wLjA3ODg0MSwtMC41ODc3MzQgLTAuMDE0MzM0LC0wLjA3MTY4IC0wLjEwMDM0NywtMC4yMDA2ODggTCA2LjI2NTY3OSwxMC40NDQ4NzUgNy45NTcyMDA5LDcuNjkyNTcwNCBRIDguMDQzMjE0Miw3LjU2MzU1NTkgOC4wNzE4ODA1LDcuNDkxODgwOCBMIDcuOTI4NTMxMiw3LjQ0ODg3OTYgUSA3Ljc5OTUxNjgsNy40MjAyMTIyIDcuNzQyMTc2NCw3LjM5MTU0NDcgNy40MTI0NzMzLDcuMjQ4MTk1NCA3LjE0NzI3NzUsNi45OTczMzQ0IDYuODgyMDgwNiw2Ljc0NjQ3MzUgNi43Mzg3MzEzLDYuNDMxMTA1MiA2LjQ5NTAzODMsNS45MTUwNDczIDYuNjA5NzE2OSw1LjM1NTk4NSA2LjY2NzA1MTcsNS4wMjYyODIgNi44NTM0MTEsNC43Mzk1ODM0IDcuMDM5NzY0Nyw0LjQ1Mjg4NDggNy4yOTc3OTM3LDQuMjUyMTk2MiA3LjgyODE4NjQsMy44NjUxNTI4IDguNDczMjU3NywzLjg2NTE1MjggOC44NjAzMDExLDMuODUwODE5MSA5LjIyNTg0MTcsNC4wMDEzMzQxIDkuNTkxMzgyMyw0LjE1MTg1MTQgOS44NjM3NDYxLDQuNDM4NTUgcSAwLjI0MzY5MjksMC4yMjkzNTgyIDAuMzc5ODc0OSwwLjU1MTg5NDQgMC4xMzYxODEsMC4zMjI1MzYxIDAuMTUwNTE3LDAuNjUyMjM5MiAwLDAuMjQzNjk0MSAtMC4wODYwMSwwLjYwMjA2NjggbCAxLjAwMzQ0NSwwLjI3MjM2MzggMC4xMTQ2NzksMC4wMTQzMzQgUSAxMS42MjY5MzgsNS45MTUwNDYyIDExLjUyNjU5Niw1LjI5ODY0NDYgMTEuNDY5MjYxLDQuODI1NTkyMyAxMS4yNDcwNjYsNC4zODgzNzY1IDExLjAyNDg3NCwzLjk1MTE2MDYgMTAuNjgwODM3LDMuNjIxNDU3NiAxMC4wNzg3NjksMy4wMTkzOTA4IDkuMjA0MzQsMi44MTg3MDIyIDkuMTE4MzI2NywyLjc5MDAzNDggOC45NDYzMTExLDIuNzYxMzU2MyBMIDguNzQ1NjIxNCwyLjczMjY4ODkgWiBtIDAuMjU4MDI3OSwxLjc3NzUzMDggcSAtMC4yNDM2OTMxLDAgLTAuNDg3Mzg3MiwwLjEwMDM0NyBRIDcuNzQyMTc0Miw0LjcxMDkxNDggNy41NDg2NTI1LDQuOTA0NDM0MyA3LjM1NTEzMTksNS4wOTc5NTUgNy4yNjkxMjE4LDUuMzU1OTgzOSA3LjE1NDQ0MjIsNS42NDI2ODI1IDcuMjExNzg2OCw1Ljk4NjcyMTQgcSAwLjA0MzAwMSwwLjI1ODAyNzkgMC4yMDA2ODg1LDAuNDg3Mzg3MiAwLjE1NzY4NDEsMC4yMjkzNTkzIDAuNDAxMzc4MywwLjM1ODM3MzggMC4zNDQwMzc4LDAuMjE1MDIzNCAwLjgwMjc1NTMsMC4xODYzNTI2IDAuMDQzMDAxLDAuMDU3MzQ2IDAuMDcxNjgsMC4xMTQ2ODA4IEwgMTAuMjkzOCw5Ljg3MTQ4NiBxIDAuMDQzLDAuMDg2MDEzIDAuMDg2MDEsMC4xMjkwMTQgMC4zODcwNDQsLTAuMzcyNzA3IDAuODM4NTk0LC0wLjUwODg4OTQgMC40NTE1NTEsLTAuMTM2MTgxNCAwLjkzMTc3LC0wLjA1MDE3MyAwLjQ4MDIyLDAuMDg2MDEzIDAuODM4NTk0LDAuMzcyNzA3NiAwLjQ1ODcxNywwLjM1ODM3MjggMC42MjM1NywwLjg4MTU5ODggMC4xNjQ4NTEsMC41MjMyMjUgMC4wMDcxLDEuMDUzNjE3IC0wLjExNDY4LDAuNDAxMzc4IC0wLjQwODU0NSwwLjcxNjc0NyAtMC4yOTM4NjcsMC4zMTUzNjggLTAuNjk1MjQ1LDAuNDczMDUyIC0wLjQ0NDM4MywwLjE4NjM1NCAtMC45NTMyNzMsMC4xMjE4NDcgLTAuNTA4ODksLTAuMDY0NTEgLTAuOTEwMjY3LC0wLjM1MTIwNiAtMC4wNTczNCwtMC4wNDMgLTAuMTg2MzU0LC0wLjE0MzM0OSBsIC0wLjA3MTY4LC0wLjA4NjAxIHEgLTAuMDU3MzQsMC4wNDMgLTAuMTU3Njg1LDAuMTQzMzUgbCAtMC42NzM3Mzk5LDAuNjU5NDA2IHEgMC4zMjk3MDMsMC4zMjk3MDQgMC43NDU0MTQ5LDAuNTUxODk0IDAuNDE1NzEzLDAuMjIyMTkyIDAuODc0NDMsMC4zMDgyMDIgbCAwLjMxNTM2OSwwLjA1NzM1IGggMC41NTkwNjIgbCAwLjQwMTM3OCwtMC4wNzE2OCBxIDAuOTE3NDM1LC0wLjE4NjM1NCAxLjU3Njg0MiwtMC44NjAwOTYgMC4zMTUzNjksLTAuMzE1MzY4IDAuNTIzMjI1LC0wLjcyMzkxNCAwLjIwNzg1NywtMC40MDg1NDUgMC4yNzk1MzEsLTAuODUyOTI4IDAuMDcxNjgsLTAuNTE2MDU3IC0wLjAyODY3LC0xLjAyNDk0OCBRIDE0LjcwODkzOSwxMC4xNTgxODEgMTQuNDUwOTE0LDkuNzQyNDY4MyAxNC4xOTI4ODUsOS4zMjY3NTUyIDEzLjgyMDE3Niw5LjAxMTM4NyAxMy40NDc0NjksOC42OTYwMTc3IDEzLjAwMzA4Niw4LjUwOTY2MzkgMTIuNTE1Njk4LDguMzA4OTc1MyAxMS45OTI0NzMsOC4yODAzMDU3IDExLjQ2OTI0OCw4LjI1MTYzODMgMTAuOTY3NTI1LDguMzgwNjUyNyBMIDEwLjcyMzgzMiw4LjQ1MjMzMjMgOS41NjI3MDM5LDYuNDg4NDQ2NyBRIDkuNzQ5MDU3Nyw2LjIxNjA4MjkgOS43OTIwNjMyLDUuOTU4MDU0IDkuODYzNzMxOCw1LjQ5OTMzNjUgOS42MDU3MDg0LDUuMDk3OTU4MyA5LjM3NjM1MDIsNC43Mzk1ODU2IDguOTc0OTcxOSw0LjU5NjIzNjMgOC43MzEyNzc4LDQuNTEwMjIzIDguNDczMjQ4OSw0LjUxMDIyMyBaIE0gNC40MzA4MDU3LDguMjk0NjQxNiBRIDMuNTk5MzgwNiw4LjQ4MDk5NjUgMi45OTczMTI3LDkuMDU0MzkzNiAyLjMyMzU3MTgsOS42ODUxMzAxIDIuMTIyODc3OCwxMC41NTk1NjEgbCAtMC4wNTczMTMsMC4zNDQwMzggdiAwLjUzMDM5MiBsIDAuMDI4NzExLDAuMTU3Njg0IHEgMC4wMjg3MTIsMC4xODYzNTUgMC4wNTczMTMsMC4yODY2OTkgMC4xMDAzMzYsMC40NDQzODMgMC4zNTEyMDE0LDAuODQ1NzYyIDAuMjUwODYyMSwwLjQwMTM3NyAwLjYwOTIzNDgsMC43MDI0MTEgMC43MTY3NDY1LDAuNTg3NzMyIDEuNjQ4NTE3NCwwLjcwMjQxMSAwLjU0NDcyNjQsMC4wNTczNCAxLjA4OTQ1NCwtMC4wNjQ1MSAwLjU0NDcyNzUsLTAuMTIxODQ3IDEuMDAzNDQ1LC0wLjQwODU0NSAwLjc1OTc1MDksLTAuNDg3Mzg4IDEuMTE4MTI0NywtMS4zMDQ0OCAwLjEwMDM0NywtMC4yNDM2OTMgMC4yMDA2ODg2LC0wLjY0NTA3MSAwLjA4NjAxMywtMC4wMTQzMyAwLjI1ODAyODksMCBsIDEuNzkxODY2NCwwLjAyODY3IHEgMC4yMDA2ODgsMCAwLjI4NjY5NywwLjAxNDMzIDAuMTU3Njg0LDAuMjg2NyAwLjM1ODM3NCwwLjQ0NDM4NCAwLjIwMDY4OSwwLjE1NzY4MyAwLjQ1MTU1MSwwLjIyOTM1OCAwLjI1MDg2MSwwLjA3MTY4IDAuNTA4ODg5LDAuMDU3MzQgMC40MDEzNzksLTAuMDQzIDAuNzE2NzQ2LC0wLjI4NjY5NyAwLjM4NzA0MywtMC4zMDEwMzUgMC40NTg3MTksLTAuNzc0MDg3IDAuMDI4NjcsLTAuMjQzNjkzIC0wLjAyMTUxLC0wLjQ3MzA1MyAtMC4wNTAxNywtMC4yMjkzNTggLTAuMTkzNTIyLC0wLjQzMDA0OCAtMC4yMjkzNTgsLTAuMzE1MzY4IC0wLjU4NzczMiwtMC40NTg3MTcgLTAuMjQzNjk0LC0wLjEwMDM0NzEgLTAuNTA4ODksLTAuMDkzMTc1IC0wLjI2NTE5NiwwLjAwNzE1IC0wLjUwODg5LDAuMTA3NTA5IC0wLjQwMTM3NywwLjE3MjAxOSAtMC42MTY0MDIsMC41MzAzOTEgbCAtMC4wNDMsMC4wODYwMSBxIC0wLjIwMDY4OSwwIC0wLjYwMjA2NzIsLTAuMDE0MzMgTCA4LjMwMTI2NjEsMTAuNjU5OTA3IFEgOC4wODYyNDI3LDEwLjY0NTU3NyA3LjY4NDg2NDUsMTAuNjQ1NTc3IEwgNi45NjgxMTgsMTAuNjMxMjQ3IDcuMDExMTE5LDEwLjg3NDk0IFEgNy4xMjU4MDIzLDExLjQ5MTMzIDYuODI0NzY4OSwxMi4wMzYwNTggNi42MjQwODAzLDEyLjQyMzEgNi4yNTEzNzE3LDEyLjY4MTEyOSA1Ljg3ODY2MzEsMTIuOTM5MTU4IDUuNDM0MjgwNCwxMi45OTY0OTggNC45MzI1NTg0LDEzLjA4MjUxMSA0LjQzMDgzNTQsMTIuODg4OTg5IDMuOTI5MTEzNCwxMi42OTU0NjggMy42MTM3NDUyLDEyLjI3OTc1NCAzLjIxMjM2NjksMTEuNzc4MDMyIDMuMjI2NzAxOCwxMS4xNDcyOTYgMy4yNDEwMzU1LDEwLjYxNjkwMyAzLjU1NjQwNDgsMTAuMTU4MTg2IDMuNzQyNzU5Niw5Ljg4NTgyMDggNC4wMTUxMjIzLDkuNjk5NDY4MiA0LjI4NzQ4NjEsOS41MTMxMTM0IDQuNjAyODU0Myw5LjQyNzEwMzMgbCAwLjEyOTAxNDUsLTAuMDQzMDAxIHoiCiAgICAgICBpZD0icGF0aDEiCiAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZS13aWR0aDowLjAxNDMzNDkiIC8+PC9nPjwvc3ZnPgo=";
14+
15+
const runtime = Scratch.vm.runtime;
16+
17+
let hideFromPalette = true;
18+
let webhooks;
19+
function setupStorage() {
20+
if (!runtime.extensionStorage["cubesterWebhooks"]) {
21+
runtime.extensionStorage["cubesterWebhooks"] = {
22+
webhooks: Object.create(null),
23+
};
24+
}
25+
webhooks = runtime.extensionStorage["cubesterWebhooks"].webhooks;
26+
if (Object.keys(webhooks).length > 0) {
27+
hideFromPalette = false;
28+
}
29+
}
30+
setupStorage();
31+
runtime.on("PROJECT_LOADED", setupStorage);
32+
33+
function isURLValid(string) {
34+
try {
35+
const url = new URL(string);
36+
return url.protocol === "http:" || url.protocol === "https:";
37+
} catch (error) {
38+
return false;
39+
}
40+
}
41+
42+
function toForm(data) {
43+
const parts = [];
44+
const process = (name, value) => {
45+
if (value === null || value === undefined) {
46+
parts.push(`${encodeURIComponent(name)}=null`);
47+
} else if (
48+
typeof value === "string" ||
49+
typeof value === "number" ||
50+
typeof value === "boolean"
51+
) {
52+
parts.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
53+
} else if (Array.isArray(value)) {
54+
value.forEach((elem, index) => process(`${name}[${index}]`, elem));
55+
} else if (typeof value === "object") {
56+
Object.keys(value).forEach((key) =>
57+
process(`${name}[${key}]`, value[key])
58+
);
59+
}
60+
};
61+
Object.keys(data).forEach((key) => process(key, data[key]));
62+
return parts.join("&");
63+
}
64+
65+
class Webhooks {
66+
getInfo() {
67+
return {
68+
id: "cubesterWebhooks",
69+
name: Scratch.translate("Webhooks"),
70+
color1: "#C73A63",
71+
menuIconURI: icon,
72+
docsURI: "https://extensions.turbowarp.org/CubesterYT/Webhooks",
73+
74+
blocks: [
75+
"---",
76+
77+
{
78+
func: "addWebhook",
79+
text: Scratch.translate("Add a Webhook"),
80+
blockType: Scratch.BlockType.BUTTON,
81+
},
82+
{
83+
func: "deleteWebhook",
84+
text: Scratch.translate("Delete a Webhook"),
85+
blockType: Scratch.BlockType.BUTTON,
86+
hideFromPalette,
87+
},
88+
{
89+
opcode: "data",
90+
text: Scratch.translate("data of [WEBHOOK]"),
91+
blockType: Scratch.BlockType.REPORTER,
92+
disableMonitor: true,
93+
hideFromPalette,
94+
arguments: {
95+
WEBHOOK: {
96+
type: Scratch.ArgumentType.STRING,
97+
menu: "WEBHOOKS",
98+
},
99+
},
100+
},
101+
{
102+
opcode: "setData",
103+
text: Scratch.translate(
104+
"set data of [WEBHOOK] to [JSON] and type to [TYPE]"
105+
),
106+
blockType: Scratch.BlockType.COMMAND,
107+
hideFromPalette,
108+
arguments: {
109+
WEBHOOK: {
110+
type: Scratch.ArgumentType.STRING,
111+
menu: "WEBHOOKS",
112+
},
113+
JSON: {
114+
type: Scratch.ArgumentType.STRING,
115+
defaultValue: '{"key":"value"}',
116+
},
117+
TYPE: {
118+
type: Scratch.ArgumentType.STRING,
119+
menu: "TYPE",
120+
},
121+
},
122+
},
123+
{
124+
opcode: "postData",
125+
text: Scratch.translate("send data to [WEBHOOK]"),
126+
blockType: Scratch.BlockType.COMMAND,
127+
hideFromPalette,
128+
arguments: {
129+
WEBHOOK: {
130+
type: Scratch.ArgumentType.STRING,
131+
menu: "WEBHOOKS",
132+
},
133+
},
134+
},
135+
],
136+
menus: {
137+
WEBHOOKS: {
138+
acceptReporters: false,
139+
items: "_webhookMenu",
140+
},
141+
TYPE: {
142+
acceptReporters: false,
143+
items: ["application/json", "application/x-www-form-urlencoded"],
144+
},
145+
},
146+
};
147+
}
148+
149+
async addWebhook() {
150+
const list = Object.keys(webhooks);
151+
let name;
152+
do {
153+
name = await prompt(Scratch.translate("Enter Webhook name:"));
154+
if (name === null) {
155+
return;
156+
}
157+
if (name === "") {
158+
alert(
159+
Scratch.translate(
160+
"Please enter a name with at least one character!"
161+
)
162+
);
163+
}
164+
if (list.includes(name)) {
165+
alert(
166+
Scratch.translate(
167+
"This Webhook already exists! Please try a different name."
168+
)
169+
);
170+
}
171+
} while (list.includes(name) || name === "");
172+
let URL;
173+
do {
174+
URL = await prompt(Scratch.translate("Enter Webhook URL:"));
175+
if (URL === null) {
176+
return;
177+
}
178+
if (!isURLValid(URL)) {
179+
alert(
180+
Scratch.translate(
181+
"Invalid URL! Please make sure you provide a valid URL."
182+
)
183+
);
184+
}
185+
} while (!isURLValid(URL));
186+
webhooks[name] = {
187+
URL,
188+
DATA: Object.create(null),
189+
TYPE: "application/json",
190+
};
191+
hideFromPalette = false;
192+
Scratch.vm.extensionManager.refreshBlocks("cubesterWebhooks");
193+
}
194+
async deleteWebhook() {
195+
const list = Object.keys(webhooks);
196+
let name;
197+
do {
198+
name = await prompt(Scratch.translate("Enter Webhook name:"));
199+
if (name === null) {
200+
return;
201+
}
202+
if (!list.includes(name)) {
203+
alert(
204+
Scratch.translate(
205+
"This Webhook does not exist. Please enter a valid webhook name."
206+
)
207+
);
208+
}
209+
} while (!list.includes(name));
210+
delete webhooks[name];
211+
if (Object.keys(webhooks).length === 0) {
212+
hideFromPalette = true;
213+
}
214+
Scratch.vm.extensionManager.refreshBlocks("cubesterWebhooks");
215+
}
216+
data(args) {
217+
if (!webhooks[args.WEBHOOK]) {
218+
return "";
219+
}
220+
switch (webhooks[args.WEBHOOK].TYPE) {
221+
case "application/x-www-form-urlencoded":
222+
return toForm(webhooks[args.WEBHOOK].DATA);
223+
default:
224+
return JSON.stringify(webhooks[args.WEBHOOK].DATA);
225+
}
226+
}
227+
setData(args) {
228+
if (!webhooks[args.WEBHOOK]) {
229+
return;
230+
}
231+
try {
232+
webhooks[args.WEBHOOK].DATA = JSON.parse(
233+
Scratch.Cast.toString(args.JSON)
234+
);
235+
} catch (error) {}
236+
webhooks[args.WEBHOOK].TYPE = args.TYPE;
237+
}
238+
postData(args) {
239+
if (!webhooks[args.WEBHOOK]) {
240+
return;
241+
}
242+
Scratch.fetch(webhooks[args.WEBHOOK].URL, {
243+
method: "POST",
244+
headers: {
245+
"Content-type": webhooks[args.WEBHOOK].TYPE,
246+
},
247+
body:
248+
webhooks[args.WEBHOOK].TYPE === "application/json"
249+
? JSON.stringify(webhooks[args.WEBHOOK].DATA)
250+
: toForm(webhooks[args.WEBHOOK].DATA),
251+
});
252+
}
253+
254+
_webhookMenu() {
255+
const list = Object.keys(webhooks);
256+
if (list.length > 0) {
257+
return list;
258+
} else {
259+
return [""];
260+
}
261+
}
262+
}
263+
264+
Scratch.extensions.register(new Webhooks());
265+
})(Scratch);

extensions/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@
9191
"vercte/dictionaries",
9292
"godslayerakp/http",
9393
"godslayerakp/ws",
94+
"CubesterYT/Webhooks",
9495
"Lily/CommentBlocks",
9596
"veggiecan/LongmanDictionary",
96-
"CubesterYT/TurboHook",
9797
"Alestore/nfcwarp",
9898
"NishiOwO/dectalk",
9999
"steamworks",

0 commit comments

Comments
 (0)