class LocalStoragePseudoCloud {
constructor() {
this.peer = null;
this.conn = null;
this.isHost = false;
this.VAR_PREFIX = 'pm_var_';
this.LIST_PREFIX = 'pm_list_';
this.LOG_KEY = 'pm_local_log';
this.BANNED_USERS_KEY = 'pm_banned_users';
this.WARNINGS_KEY = 'pm_user_warnings';
this.BANNED_WORDS_KEY = 'pm_banned_words';
this.ALLOWED_USER = 'ralsei-';
this.messageCooldown = 2000; // 2 seconds per message
this.lastMessageTime = {};
// Default banned words
const defaultBannedWords = ["hate rals","hate ral","hate ralsii","hate ralsie","hate alsyi","hate boss_dunkun","fuck","bitch","hate ralsei","fucking",67,"6 7",69,"sex","s3x","p0rn","porn","six seven","sixty seven","6-7","fuk","fuc","fck","fkk","fak","nigger","niger"," n1993r","n1gg3r","nigg3r","n1ger","n193r","n i g g e r","niger","niga","n1g4","n1ga","nig4","n i g 4","n 1 g a","nigar","n1g4r","/ban @s"];
if(!localStorage.getItem(this.BANNED_WORDS_KEY)){
localStorage.setItem(this.BANNED_WORDS_KEY, JSON.stringify(defaultBannedWords));
}
// Auto-load PeerJS
if (typeof Peer === 'undefined') {
const s = document.createElement('script');
s.src = 'https://unpkg.com/[email protected]/dist/peerjs.min.js';
s.onload = () => console.log('PeerJS loaded.');
document.head.appendChild(s);
}
}
getInfo() {
return {
id: 'LocalStoragePseudoCloud',
name: 'Local Storage Cloud (WebRTC) Advanced',
color1: '#5ba58c',
color2: '#4b8d78',
blocks: [
// VARIABLES
{ opcode: 'setVar', blockType: Scratch.BlockType.COMMAND, text: 'set variable [KEY] to [VALUE]', arguments: { KEY: { type: Scratch.ArgumentType.STRING }, VALUE: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'getVar', blockType: Scratch.BlockType.REPORTER, text: 'get variable [KEY]', arguments: { KEY: { type: Scratch.ArgumentType.STRING } } },
// LISTS
{ opcode: 'createList', blockType: Scratch.BlockType.COMMAND, text: 'create list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'addToList', blockType: Scratch.BlockType.COMMAND, text: 'add [ITEM] to list [LIST]', arguments: { ITEM: { type: Scratch.ArgumentType.STRING }, LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'setItem', blockType: Scratch.BlockType.COMMAND, text: 'set item [INDEX] of list [LIST] to [ITEM]', arguments: { INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, LIST: { type: Scratch.ArgumentType.STRING }, ITEM: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'deleteItem', blockType: Scratch.BlockType.COMMAND, text: 'delete item [INDEX] of list [LIST]', arguments: { INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'getItem', blockType: Scratch.BlockType.REPORTER, text: 'item [INDEX] of list [LIST]', arguments: { INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'listLength', blockType: Scratch.BlockType.REPORTER, text: 'length of list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'clearList', blockType: Scratch.BlockType.COMMAND, text: 'clear list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'listContains', blockType: Scratch.BlockType.BOOLEAN, text: 'does list [LIST] contain [ITEM]', arguments: { LIST: { type: Scratch.ArgumentType.STRING }, ITEM: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'listAsArray', blockType: Scratch.BlockType.REPORTER, text: 'list [LIST] as array', arguments: { LIST: { type: Scratch.ArgumentType.STRING } } },
// CHAT-FRIENDLY BLOCKS
{ opcode: 'sendMessage', blockType: Scratch.BlockType.COMMAND, text: 'send message [TEXT] as [USERNAME] in chat [CHAT]', arguments: { TEXT: { type: Scratch.ArgumentType.STRING }, USERNAME: { type: Scratch.ArgumentType.STRING }, CHAT: { type: Scratch.ArgumentType.STRING, defaultValue: 'general' } } },
{ opcode: 'lastMessage', blockType: Scratch.BlockType.REPORTER, text: 'last message in chat [CHAT]', arguments: { CHAT: { type: Scratch.ArgumentType.STRING, defaultValue: 'general' } } },
{ opcode: 'allMessages', blockType: Scratch.BlockType.REPORTER, text: 'all messages in chat [CHAT]', arguments: { CHAT: { type: Scratch.ArgumentType.STRING, defaultValue: 'general' } } },
{ opcode: 'clearChat', blockType: Scratch.BlockType.COMMAND, text: 'clear chat [CHAT]', arguments: { CHAT: { type: Scratch.ArgumentType.STRING, defaultValue: 'general' } } },
{ opcode: 'messageCount', blockType: Scratch.BlockType.REPORTER, text: 'messages count in chat [CHAT]', arguments: { CHAT: { type: Scratch.ArgumentType.STRING, defaultValue: 'general' } } },
// BAN / UNBAN / BANNED WORDS
{ opcode: 'banUser', blockType: Scratch.BlockType.COMMAND, text: 'ban user [USERNAME]', arguments: { USERNAME: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'unbanUser', blockType: Scratch.BlockType.COMMAND, text: 'unban user [USERNAME]', arguments: { USERNAME: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'getBannedWords', blockType: Scratch.BlockType.REPORTER, text: 'get banned words list' },
{ opcode: 'setBannedWords', blockType: Scratch.BlockType.COMMAND, text: 'set banned words list to [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING } } },
// LOGGING
{ opcode: 'clearLogs', blockType: Scratch.BlockType.COMMAND, text: 'clear logs' },
{ opcode: 'logsAsArray', blockType: Scratch.BlockType.REPORTER, text: 'logs as array' },
// CLOUD
{ opcode: 'startHost', blockType: Scratch.BlockType.COMMAND, text: 'start cloud host' },
{ opcode: 'connectCloud', blockType: Scratch.BlockType.COMMAND, text: 'connect to cloud with code [CODE]', arguments: { CODE: { type: Scratch.ArgumentType.STRING } } },
{ opcode: 'cloudStatus', blockType: Scratch.BlockType.REPORTER, text: 'cloud status' }
]
};
}
// ---------- HELPERS ----------
_log(action, user=''){ const log = JSON.parse(localStorage.getItem(this.LOG_KEY)||'[]'); log.push({time:new Date().toISOString(),user,action}); localStorage.setItem(this.LOG_KEY,JSON.stringify(log)); }
_send(type,payload){ if(this.conn && this.conn.open) this.conn.send({type,payload}); }
_syncAll(){ const vars={}, lists={}; for(let k in localStorage){ if(k.startsWith(this.VAR_PREFIX)) vars[k]=localStorage.getItem(k); if(k.startsWith(this.LIST_PREFIX)) lists[k]=localStorage.getItem(k); } this._send('sync',{vars,lists}); }
// ---------- VARIABLES ----------
setVar(a){ localStorage.setItem(this.VAR_PREFIX+a.KEY,a.VALUE); this._log(`set variable ${a.KEY}=${a.VALUE}`); this._send('var',a); }
getVar(a){ return localStorage.getItem(this.VAR_PREFIX+a.KEY)||''; }
// ---------- LISTS ----------
createList(a){ localStorage.setItem(this.LIST_PREFIX+a.LIST,'[]'); this._log(`created list ${a.LIST}`); this._send('list',{name:a.LIST,value:[]}); }
_getList(name){ return JSON.parse(localStorage.getItem(this.LIST_PREFIX+name)||'[]'); }
_setList(name,arr){ localStorage.setItem(this.LIST_PREFIX+name,JSON.stringify(arr)); this._log(`set list ${name}=${JSON.stringify(arr)}`); this._send('list',{name,value:arr}); }
addToList(a){ const l=this._getList(a.LIST); l.push(a.ITEM); this._setList(a.LIST,l); }
setItem(a){ const l=this._getList(a.LIST); if(a.INDEX>0 && a.INDEX<=l.length){ l[a.INDEX-1]=a.ITEM; this._setList(a.LIST,l); } }
deleteItem(a){ const l=this._getList(a.LIST); if(a.INDEX>0 && a.INDEX<=l.length){ l.splice(a.INDEX-1,1); this._setList(a.LIST,l); } }
getItem(a){ return this._getList(a.LIST)[a.INDEX-1]||''; }
listLength(a){ return this._getList(a.LIST).length; }
clearList(a){ this._setList(a.LIST,[]); }
listContains(a){ return this._getList(a.LIST).includes(a.ITEM); }
listAsArray(a){ return JSON.stringify(this._getList(a.LIST)); }
// ---------- CHAT & BANNED WORDS ----------
_chatListName(chat){ return `chat_${chat||'general'}`; }
sendMessage(a){
const user = a.USERNAME||'guest';
const chat = a.CHAT||'general';
const now = Date.now();
if(this.lastMessageTime[user] && now-this.lastMessageTime[user]<this.messageCooldown) return;
this.lastMessageTime[user]=now;
// Check banned users
const banned = JSON.parse(localStorage.getItem(this.BANNED_USERS_KEY)||'[]');
if(banned.includes(user)) return;
// Check banned words
const bannedWords = JSON.parse(localStorage.getItem(this.BANNED_WORDS_KEY)||'[]');
let messageText = a.TEXT.toString().toLowerCase();
let warningCount = JSON.parse(localStorage.getItem(this.WARNINGS_KEY)||'{}');
for(const word of bannedWords){
if(messageText.includes(word.toString().toLowerCase())){
warningCount[user] = (warningCount[user]||0)+1;
localStorage.setItem(this.WARNINGS_KEY, JSON.stringify(warningCount));
this._log(`warning ${warningCount[user]} for user ${user} (word: ${word})`, user);
if(warningCount[user]>=3){
banned.push(user);
localStorage.setItem(this.BANNED_USERS_KEY, JSON.stringify(banned));
this._log(`auto banned user ${user} for repeated banned words`, user);
}
return; // do not send message containing banned word
}
}
const chatList=this._getList(this._chatListName(chat));
chatList.push(`${user}: ${a.TEXT}`);
this._setList(this._chatListName(chat), chatList);
this._log(`send message as ${user}: ${a.TEXT} in chat ${chat}`,user);
}
lastMessage(a){ return this._getList(this._chatListName(a.CHAT))[this._getList(this._chatListName(a.CHAT)).length-1]||''; }
allMessages(a){ return this._getList(this._chatListName(a.CHAT)); }
clearChat(a){ this._setList(this._chatListName(a.CHAT),[]); this._log(`cleared chat ${a.CHAT}`); }
messageCount(a){ return this._getList(this._chatListName(a.CHAT)).length; }
// ---------- BAN / UNBAN ----------
banUser(a){ if(this.getVar({KEY:'username'})!==this.ALLOWED_USER)return; const banned=JSON.parse(localStorage.getItem(this.BANNED_USERS_KEY)||'[]'); if(!banned.includes(a.USERNAME))banned.push(a.USERNAME); localStorage.setItem(this.BANNED_USERS_KEY,JSON.stringify(banned)); this._log(`banned user ${a.USERNAME}`,this.ALLOWED_USER); }
unbanUser(a){ if(this.getVar({KEY:'username'})!==this.ALLOWED_USER)return; let banned=JSON.parse(localStorage.getItem(this.BANNED_USERS_KEY)||'[]'); banned=banned.filter(u=>u!==a.USERNAME); localStorage.setItem(this.BANNED_USERS_KEY,JSON.stringify(banned)); this._log(`unbanned user ${a.USERNAME}`,this.ALLOWED_USER); }
getBannedWords(){ return JSON.stringify(JSON.parse(localStorage.getItem(this.BANNED_WORDS_KEY)||'[]')); }
setBannedWords(a){ localStorage.setItem(this.BANNED_WORDS_KEY,JSON.stringify(a.LIST.split(','))); }
// ---------- LOGS ----------
clearLogs(){ localStorage.setItem(this.LOG_KEY,'[]'); }
logsAsArray(){ return JSON.stringify(JSON.parse(localStorage.getItem(this.LOG_KEY)||'[]')); }
// ---------- CLOUD ----------
startHost(){ if(typeof Peer==='undefined'){alert('PeerJS not loaded yet. Wait a second.'); return;} this.isHost=true; this.peer=new Peer(); this.peer.on('open',id=>{alert('Cloud code: '+id);console.log('Hosting cloud with code:',id);}); this.peer.on('connection',conn=>{this.conn=conn;conn.on('open',()=>this._syncAll());conn.on('data',d=>this._handleData(d));}); }
connectCloud(a){ if(typeof Peer==='undefined'){alert('PeerJS not loaded yet. Wait a second.'); return;} this.peer=new Peer(); this.peer.on('open',()=>{this.conn=this.peer.connect(a.CODE);this.conn.on('data',d=>this._handleData(d));}); }
_handleData(d){ if(d.type==='sync'){Object.entries(d.payload.vars).forEach(([k,v])=>localStorage.setItem(k,v)); Object.entries(d.payload.lists).forEach(([k,v])=>localStorage.setItem(k,v));} if(d.type==='var') localStorage.setItem(this.VAR_PREFIX+d.payload.KEY,d.payload.VALUE); if(d.type==='list') localStorage.setItem(this.LIST_PREFIX+d.payload.name,JSON.stringify(d.payload.value)); }
cloudStatus(){ if(!this.peer)return'offline'; if(this.isHost)return'hosting'; if(this.conn&&this.conn.open)return'connected'; return'connecting'; }
}
Scratch.extensions.register(new LocalStoragePseudoCloud());











0 comments