diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1913a71
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+## BlockLive: Real-Time Collaboration for Scratch
+
+
+
+**BlockLive** is a Chrome extension that lets you work together with friends on Scratch projects in real-time! No more tedious remixing and sharing - BlockLive keeps everyone on the same page with instant synchronization.
+
+**Features:**
+
+* **Real-time Collaboration:** See changes made by your collaborators instantly, from block edits to asset uploads and project name changes.
+* **Easy Setup:** Install the extension, open a Scratch project, and invite your friends using the BlockLive interface.
+* **Collaborative Editing:** Everyone can work on the project simultaneously, fostering creative teamwork.
+* **Websocket Powered:** BlockLive uses websockets for efficient and low-latency communication.
+
+**Installation:**
+
+1. Visit [the Chrome Webstore](https://chromewebstore.google.com/detail/blocklive-scratch-realtim/gelkmljpoacdjkjkcfekkmgkpnmeomlk).
+2. Click "Add to Chrome" and follow the on-screen instructions.
+
+**Getting Started:**
+
+1. **Create a new project or open an existing project**:
+ - Start a new project or open a project you want to collaborate on.
+
+2. **Enable BlockLive**:
+ - Click on the BlockLive Share button in the Scratch interface to enable real-time collaboration.
+
+3. **Invite collaborators**:
+ - Invite your friends to the project using the menu in the popup.
+
+4. **Collaborate in real-time**:
+ - Work together with your collaborators, see their changes live, and communicate using the built-in chat feature.
+
+**Important Notes:**
+
+* **Allowlist:** For security reasons, you need to add collaborators to your allowlist before they can join your project.
+* **Development:** BlockLive is still under development. You may encounter bugs or limitations.
+
+**Contributing:**
+
+We welcome contributions from the community! If you'd like to help improve BlockLive, you can find the source code on [GitHub](https://github.com/BlockliveScratch/blocklive).
+
+**Support the Project:**
+
+If you find BlockLive useful, consider supporting the development by donating us on [buymeacoffee](https://buymeacoffee.com/ilhp10).
+
+**We hope you enjoy collaborating on Scratch with BlockLive!**
diff --git a/backend/scratch-auth.js b/backend/scratch-auth.js
index 24a92e7..612cf5b 100644
--- a/backend/scratch-auth.js
+++ b/backend/scratch-auth.js
@@ -6,7 +6,9 @@ export const freePassesPath = 'storage/freePasses.json'
export const failedAuthLog = {}
export const secondTimeSuccessAuthLog = {};
-
+let cachedComments = [];
+let lastTimeCheckedComments = 0;
+const commentsRateLimit = 1000 * 5;
function logAuth(username, success, word, info) {
if (!username) { return; }
@@ -43,6 +45,12 @@ function getAuthProjectId() {
return authProjects[idIndex];
}
+async function getAuthProjectData() {
+ const projectId = getAuthProjectId();
+ const data = await (await fetch(`https://api.scratch.mit.edu/projects/${projectId}`)).json();
+ return { projectId, projectUsername: data.author.username };
+}
+
let userManager
let sessionManager
export function setPaths(app, userManagerr, sessionManagerr) {
@@ -62,6 +70,9 @@ export function setPaths(app, userManagerr, sessionManagerr) {
const CLOUD_WAIT = 1000 * 5;
app.get('/verify/userToken', async (req, res) => { // ?code=000000&method=cloud|CLOUDs
+ if (bypassUserAuth) {
+ return res.send({ freepass: true })
+ }
try {
let clientCode = req.query.code
if (!clientCode) { res.send({ err: 'no client code included' }); return }
@@ -71,6 +82,8 @@ export function setPaths(app, userManagerr, sessionManagerr) {
res.send({ err: 'client code not found', clientCode });
return;
}
+
+ await sleep(500) // Makes sure cloud is ready to be called and we don't need to spend extra time on retrying
let cloud = await getVerificationCloud(tempCode)
if (!cloud || cloud?.err) {
@@ -158,6 +171,30 @@ async function getVerificationCloud(tempCode) {
return cloud;
}
+async function checkComments() {
+ if (lastTimeCheckedComments + commentsRateLimit > Date.now()) {
+ return cachedComments;
+ }
+
+ let { projectUsername, projectId } = await getAuthProjectData();
+
+ const data = await (await fetch(`https://api.scratch.mit.edu/users/${projectUsername}/projects/${projectId}/comments?offset=0&limit=40&rand=${Math.random()}`)).json();
+ cachedComments = data;
+ lastTimeCheckedComments = Date.now();
+ return data;
+}
+
+async function verifyCommentCode(code, retried=false) {
+ if (retried) { await sleep(commentsRateLimit); }
+ const data = await checkComments();
+ const comment = data?.comments?.filter(c => c.content == code).reverse()[0];
+ if (!comment) {
+ if (!retried) { return await verifyCommentCode(code, true) }
+ return null;
+ }
+ return comment;
+}
+
// export let freePasses = {} // username : passtime
diff --git a/extension/scripts/editor.js b/extension/scripts/editor.js
index 4f2bd1d..f6ce26b 100755
--- a/extension/scripts/editor.js
+++ b/extension/scripts/editor.js
@@ -147,7 +147,7 @@ async function startBlocklive(creatingNew) {
})
}
if(creatingNew) {
- addToCredits('Get BIocklive for Live Collabs #bl')
+ addToCredits('Get Blocklive for Live Collabs #bl')
}
}
@@ -214,6 +214,8 @@ async function joinExistingBlocklive(id) {
liveMessage({meta:"joinSession"}) // join sessionManager session
readyToRecieveChanges = true
pauseEventHandling = false;
+
+ reloadOnlineUsers();
// hackyRefreshFlyoutVariables()
setTimeout(BL_UTILS.refreshFlyout,100) // todo figure way other than timeout
@@ -2780,7 +2782,7 @@ let blActivateClick = async ()=>{
await refreshShareModal()
// add blocklive ref in instructions credits
- addToCredits('Get BIocklive for Live Collabs #bl')
+ addToCredits('Get Blocklive for Live Collabs #bl')
// stop spinny
document.querySelector('loader.blockliveloader').style.display = 'none'
diff --git a/extension/scripts/mystuff.js b/extension/scripts/mystuff.js
index fadcc13..a18a8f6 100755
--- a/extension/scripts/mystuff.js
+++ b/extension/scripts/mystuff.js
@@ -400,21 +400,28 @@ chrome.runtime.sendMessage(exId, { meta: 'getUsernamePlus' }, async (userData) =
})
-
+ let shouldShowBanner = true;
+ async function updateShouldHideBanner() {
+ const res = await fetch('https://spore.us.to/api/verify/bypass');
+ const bypass = (await res.text()) === 'true';
+ shouldShowBanner = !bypass;
+ }
+ updateShouldHideBanner();
chrome.runtime.sendMessage(exId, { meta: 'verifying' }, (verifying) => {
console.log('vf',verifying)
console.log('verifying',verifying)
- if (verifying=='nocon'){
+ if (verifying=='nocon' && shouldShowBanner) {
// defaultAddHideBlockliveButton()
document.querySelector('.box-head').insertAdjacentHTML('afterend', `