class LocalStoragePlusCloudFinal {
constructor() {
this.USER_KEY = 'pm_local_user';
this.LOG_KEY = 'pm_local_log';
this.ALLOWED_USER = 'ralsei';
}
getInfo() {
return {
id: 'LocalStoragePlusCloudFinal', // ✅ valid ID
name: 'Local Storage+ PseudoCloud Final',
color1: '#5ba58c',
color2: '#4b8d78',
blocks: [
// User
{ opcode: 'setUser', blockType: Scratch.BlockType.COMMAND, text: 'set username to [USER]', arguments: { USER: { type: Scratch.ArgumentType.STRING, defaultValue: 'guest' } } },
// Variables
{ opcode: 'setValue', blockType: Scratch.BlockType.COMMAND, text: 'set variable [KEY] to [VALUE]', arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'score' }, VALUE: { type: Scratch.ArgumentType.STRING, defaultValue: '0' } } },
{ opcode: 'getValue', blockType: Scratch.BlockType.REPORTER, text: 'get variable [KEY]', arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'score' } } },
// Lists
{ opcode: 'createList', blockType: Scratch.BlockType.COMMAND, text: 'create list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'addToList', blockType: Scratch.BlockType.COMMAND, text: 'add [ITEM] to list [LIST]', arguments: { ITEM: { type: Scratch.ArgumentType.STRING, defaultValue: 'thing' }, LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'getFromList', blockType: Scratch.BlockType.REPORTER, text: 'item [INDEX] of list [LIST]', arguments: { INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'listLength', blockType: Scratch.BlockType.REPORTER, text: 'length of list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'clearList', blockType: Scratch.BlockType.COMMAND, text: 'clear list [LIST]', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'listAsArray', blockType: Scratch.BlockType.REPORTER, text: 'list [LIST] as array', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'listContains', blockType: Scratch.BlockType.BOOLEAN, text: 'does list [LIST] contain [ITEM]', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' }, ITEM: { type: Scratch.ArgumentType.STRING, defaultValue: 'thing' } } },
// Logs
{ opcode: 'logEvent', blockType: Scratch.BlockType.COMMAND, text: 'log [TEXT]', arguments: { TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'did something' } } },
{ opcode: 'readLog', blockType: Scratch.BlockType.REPORTER, text: 'read log (ralsei only)' },
// Pseudo-Cloud Export/Import
{ opcode: 'exportList', blockType: Scratch.BlockType.REPORTER, text: 'export list [LIST] as string', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' } } },
{ opcode: 'importList', blockType: Scratch.BlockType.COMMAND, text: 'import list [LIST] from string [STRING]', arguments: { LIST: { type: Scratch.ArgumentType.STRING, defaultValue: 'my list' }, STRING: { type: Scratch.ArgumentType.STRING, defaultValue: '[]' } } },
{ opcode: 'exportLog', blockType: Scratch.BlockType.REPORTER, text: 'export log as string' },
{ opcode: 'importLog', blockType: Scratch.BlockType.COMMAND, text: 'import log from string [STRING]', arguments: { STRING: { type: Scratch.ArgumentType.STRING, defaultValue: '[]' } } },
// Clipboard copy for pseudo-cloud
{ opcode: 'copyToClipboard', blockType: Scratch.BlockType.COMMAND, text: 'copy [TEXT] to clipboard', arguments: { TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: '' } } },
]
};
}
_key(k) { return 'pm_local_' + k; }
_listKey(name) { return 'pm_list_' + name; }
_getUser() { return localStorage.getItem(this.USER_KEY) || ''; }
_isAllowed() { return this._getUser() === this.ALLOWED_USER; }
// ------------------ User ------------------
setUser(args) { localStorage.setItem(this.USER_KEY, args.USER); }
// ------------------ Variables ------------------
setValue(args) { localStorage.setItem(this._key(args.KEY), args.VALUE); }
getValue(args) { return localStorage.getItem(this._key(args.KEY)) || ''; }
// ------------------ Lists ------------------
createList(args) { localStorage.setItem(this._listKey(args.LIST), '[]'); }
_getList(name) { return JSON.parse(localStorage.getItem(this._listKey(name)) || '[]'); }
_setList(name, arr) { localStorage.setItem(this._listKey(name), JSON.stringify(arr)); }
addToList(args) { const list = this._getList(args.LIST); list.push(args.ITEM); this._setList(args.LIST, list); }
getFromList(args) { const list = this._getList(args.LIST); return list[args.INDEX-1] || ''; }
listLength(args) { return this._getList(args.LIST).length; }
clearList(args) { this._setList(args.LIST, []); }
listAsArray(args) { return JSON.stringify(this._getList(args.LIST)); }
listContains(args) { return this._getList(args.LIST).includes(args.ITEM); }
// ------------------ Logs ------------------
logEvent(args) {
const log = JSON.parse(localStorage.getItem(this.LOG_KEY) || '[]');
log.push({ time: new Date().toLocaleString(), user: this._getUser(), text: args.TEXT });
localStorage.setItem(this.LOG_KEY, JSON.stringify(log));
}
readLog() {
if (!this._isAllowed()) return '[access denied]';
const log = JSON.parse(localStorage.getItem(this.LOG_KEY) || '[]');
return log.map(e => `[${e.time}] ${e.user}: ${e.text}`).join('\n');
}
// ------------------ Pseudo-Cloud ------------------
exportList(args) { return JSON.stringify(this._getList(args.LIST)); }
importList(args) { this._setList(args.LIST, JSON.parse(args.STRING)); }
exportLog() { return localStorage.getItem(this.LOG_KEY) || '[]'; }
importLog(args) { localStorage.setItem(this.LOG_KEY, args.STRING); }
// ------------------ Clipboard ------------------
copyToClipboard(args) {
try {
navigator.clipboard.writeText(args.TEXT);
} catch(e) {
console.error("Clipboard copy failed:", e);
}
}
}
// ------------------ Safe registration ------------------
if (typeof Scratch !== "undefined" && Scratch.extensions && Scratch.extensions.register) {
Scratch.extensions.register(new LocalStoragePlusCloudFinal());
} else {
console.error("Scratch extensions API not found. Load inside PenguinMod/Scratch.");
}











28 comments