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 logo + +**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', `
⚠️ Cant connect to blocklive servers at blocklivecollab.com Check Uptime or Dont show this message again
`) } - else if (verifying) { + else if (verifying && shouldShowBanner) { defaultAddHideBlockliveButton() document.querySelector('#verifying')?.remove() document.querySelector('.box-head').insertAdjacentHTML('afterend', `
Blocklive is verifying your account ...
`) } else { if (newVerified) { return } + if (!shouldShowBanner) { return } defaultAddHideBlockliveButton() document.querySelector('.box-head').insertAdjacentHTML('afterend', `
⚠️ Blocklive could not verify your account. Reload the tab in a few seconds. If this issue continues, contact @ilhp10 or @rgantzos
`) } diff --git a/img/logowithtext.svg b/img/logowithtext.svg new file mode 100644 index 0000000..46ef0f5 --- /dev/null +++ b/img/logowithtext.svg @@ -0,0 +1 @@ +Blocklive \ No newline at end of file