A different approach to 'Currency and Economy'

… >w<

p.s. The bot update has finished, If you are interested in the changes then here they are:

  1. better support for upcoming features.
  2. better help text.
  3. admin support (I will be looking for someone who would be interested in this).
  4. better promise’s
  5. a clean look to the outputs
  6. alias’s (listed in the help text).
  7. new code ^w^
import MuckletBot, math, json, time

CHECK_COMMANDS = ["check", "c", "balance", "bal", "b", "inquire", "i", "query", "q"]
SEND_COMMANDS = ["send", "s", "give", "g", "transfer", "t", "pay", "p"]

MAX_IN_CIRCULATION = 1_000_000

def Log(message): print(f"[{time.strftime('%H:%M:%S')}] {message}")

class STORAGE:
    def __init__(self, data): self.data = data; self.password = "not the actual password"
    def get(self, id):
        self.add(id)
        return self.data.get(id).get("balance")
    def add(self, id):
        if self.data.get(id) is None: Log(f"Added user [{id}] to the storage.")
        if self.data.get(id) is None: self.data[id] = {"balance": max((MAX_IN_CIRCULATION - self.getTotal()) // 1000, 0)}
    def set(self, id, amount):
        self.add(id)
        self.data[id]["balance"] = amount
        Log(f"Set user [{id}]'s balance to [{amount}].")
    def getTotal(self):
        total = 0
        for id in self.data: total += self.data[id]["balance"]
        return total
    def multiplyAll(self, multiplier):
        for id in self.data: self.data[id]["balance"] = math.floor(self.data[id]["balance"] * multiplier)
        Log(f"Multiplied all balances by [{multiplier}].")
    def save(self):
        with open("storage.json", "w") as f: json.dump(self.data, f)

storage = STORAGE(json.load(open("storage.json", "r")))

def OnBotMessage(bot = None, data = None):
    if data.get("type") != "message":
        bot.message(data.get("sender"), f"[{data.get('message')}] is an invalid command type, I only accept messages.")
        # Log(f"Invalid command type [{data.get('type')}] from [{data.get('sender').name}].")
    else:
        Log (f"-- [Received command [{data.get('message')}] from [{data.get('sender').name}]]")
        commands = data.get("message").split(" ")
        if commands[0] in CHECK_COMMANDS:
            bot.message(data.get("sender"), f" You have [{str(storage.get(data.get('sender').id))}] SinderBucks.")
            Log(f"{data.get('sender').name} has [{str(storage.get(data.get('sender').id))}] SinderBucks.")
        elif commands[0] in SEND_COMMANDS:
            if len(commands) < 4: bot.message(data.get("sender"), f"Invalid command, please use the format: [{commands[0]} <amount> <recipient>]"); return
            recipientName = " ".join(commands[2:]).strip()
            recipientID = bot.getChar(recipientName).join().value
            if "error" in recipientID: bot.message(data.get("sender"), f"Invalid recipient [{recipientName}]. Please make sure you are also using the full name of the recipient."); return
            recipientID = recipientID.get("result").get("rid").split(".")[-1]
            try: amount = int(commands[1])
            except: bot.message(data.get("sender"), f"Invalid amount [{commands[1]}]. Please make sure you are using a valid number."); return
            if amount < 0: bot.message(data.get("sender"), f"Invalid amount [{commands[1]}]. Please make sure you are using a valid number."); return
            senderBalance = storage.get(data.get("sender").id)
            if senderBalance < amount: bot.message(data.get("sender"), f"You do not have enough money to send [{amount}] SinderBucks to [{recipientName}]."); return
            storage.set(recipientID, storage.get(recipientID) + amount)
            storage.set(data.get("sender").id, senderBalance - amount)
            bot.message(data.get("sender"), f"You have sent [{amount}] SinderBucks to [{recipientName}].")
            bot.message(recipientID, f"You have received [{amount}] SinderBucks from [{data.get('sender').name}].")
            Log(f"{data.get('sender').name} has sent [{amount}] SinderBucks to [{recipientName}].")
        elif commands[0] == "total":
            if len(commands) < 2: bot.message(data.get("sender"), f"Invalid command, please use the format: [total <password>]"); return
            if commands[1] != storage.password: bot.message(data.get("sender"), f"Invalid password [{commands[1]}]."); return
            bot.message(data.get("sender"), f"There are currently [{storage.getTotal()}] SinderBucks in circulation.")
            Log(f"{data.get('sender').name} has checked the total amount of SinderBucks in circulation.")
        elif commands[0] == "set":
            if len(commands) < 5: bot.message(data.get("sender"), f"Invalid command, please use the format: [set <recipitent> <amount> <password>")
            if ' '.join(commands[4:]) != storage.password: bot.message(data.get("sender"), f"Invalid password [{' '.join(commands[4:])}]."); return
            recipientName = " ".join(commands[1:3]).strip()
            recipientID = bot.getChar(recipientName).join().value
            if "error" in recipientID: bot.message(data.get("sender"), f"Invalid recipient [{recipientName}]. Please make sure you are also using the full name of the recipient."); return
            recipientID = recipientID.get("result").get("rid").split(".")[-1]
            try: amount = int(commands[3])
            except: bot.message(data.get("sender"), f"Invalid amount [{commands[3]}]. Please make sure you are using a valid number."); return
            # set the amount
            storage.set(recipientID, amount)
            bot.message(data.get("sender"), f"You have set [{recipientName}]'s balance to [{storage.get(recipientID)}] SinderBucks.")
            Log(f"{data.get('sender').name} has set [{recipientName}]'s balance to [{storage.get(recipientID)}] SinderBucks.")
        elif commands[0] == "get":
            if len(commands) < 4: bot.message(data.get("sender"), f"Invalid command, please use the format: [get <recipitent> <password>"); return
            if ' '.join(commands[3:]) != storage.password: bot.message(data.get("sender"), f"Invalid password [{' '.join(commands[3:])}]."); return
            recipientName = " ".join(commands[1:3]).strip()
            recipientID = bot.getChar(recipientName).join().value
            if "error" in recipientID: bot.message(data.get("sender"), f"Invalid recipient [{recipientName}]. Please make sure you are also using the full name of the recipient."); return
            recipientID = recipientID.get("result").get("rid").split(".")[-1]
            bot.message(data.get("sender"), f"[{recipientName}] has [{storage.get(recipientID)}] SinderBucks.")
            Log(f"{data.get('sender').name} has checked [{recipientName}]'s balance.")
        elif commands[0] == "multiply-all":
            if len(commands) < 3: bot.message(data.get("sender"), f"Invalid command, please use the format: [multiply-all <multiplier> <password>"); return
            if ' '.join(commands[2:]) != storage.password: bot.message(data.get("sender"), f"Invalid password [{' '.join(commands[2:])}]."); return
            try: multiplier = float(commands[1])
            except: bot.message(data.get("sender"), f"Invalid multiplier [{commands[1]}]. Please make sure you are using a valid number."); return
            storage.multiplyAll(multiplier)
            bot.message(data.get("sender"), f"You have multiplied all balances by [{multiplier}].")
        elif commands[0] in ["kill", "die", "shutdown", "stop"]:
            if len(commands) < 2: bot.message(data.get("sender"), f"Invalid command, please use the format: [{commands[0]} <password>]"); return
            if ' '.join(commands[1:]) != storage.password: bot.message(data.get("sender"), f"Invalid password [{' '.join(commands[1:])}]."); return
            bot.message(data.get("sender"), f"Shutting down...")
            bot.sleep(); bot.stop()
            Log(f"{data.get('sender').name} has shut down the bot.")
        elif commands[0] == "report":
            if len(commands) < 2: bot.message(data.get("sender"), f"Invalid command, please use the format: [report <report>]"); return
            report = ' '.join(commands[1:])
            bot.message(data.get("sender"), f"Thank you for your report, it has been sent to the developer.")
            with open("bugs.txt", "a") as f: json.dump({"report": report, "sender": data.get("sender").name, "time": time.time()}, f)
            Log(f"{data.get('sender').name} has reported the bug [{report}].")
        elif commands[0] == "suggest":
            if len(commands) < 2: bot.message(data.get("sender"), f"Invalid command, please use the format: [suggest <suggestion>]"); return
            suggestion = ' '.join(commands[1:])
            bot.message(data.get("sender"), f"Thank you for your suggestion, it has been sent to the developer.")
            with open("features.txt", "a") as f: json.dump({"suggestion": suggestion, "sender": data.get("sender").name, "time": time.time()}, f)
            Log(f"{data.get('sender').name} has suggested the feature [{suggestion}].")
        elif commands[0] == "help":
            help = f"""
**Banker Badger Commands:**

**1. main commands:**
    `message Banker = [{'/'.join(CHECK_COMMANDS)}]` - Check your character's balance.
    `message Banker = [{'/'.join(SEND_COMMANDS)}] <amount> <recipient>` - Send SinderBucks to another character.
    `message Banker = [report] <report>` - Report a bug or issue.
    `message Banker = [suggest] <suggestion>` - Suggest a feature.
    `message Banker = [help]` - Show this help message.

**2. admin commands:** 
    `message Banker = [total] <password>` - Check the total amount of SinderBucks in circulation.
    `message Banker = [set] <recipient> <amount> <password>` - Set a character's balance.
    `message Banker = [get] <recipient> <password>` - Check a character's balance.
    `message Banker = [multiply-all] <multiplier> <password>` - Multiply all character's balances by a number.
    `message Banker = [kill] <password>` - Shut down the bot.


**Example Commands:**
    `message Banker = check` - Check your character's balance.
    `message Banker = send 100 Ico Twilight` - Send 100 SinderBucks to Ico Twilight.
    `message Banker = report I can't send money to those in different areas` - Report a bug or issue.
    `message Banker = suggest I want to be able to get interest` - Suggest a feature.
    `message Banker = help` - Show this help message.


**Notes:**
    1. You must use the full name of the recipient with a space between the first and last name.
    2. You can send SinderBucks to any character, even if they are not online. But they will not receive a message saying that they have received SinderBucks unless they are online.
    3. You may use bots to send SinderBucks to other characters, but please message the developer if you do so.
    4. Ico Twilight is the developer of this bot, if you have any questions or concerns please message them on the forums.
            """
            bot.message(data.get("sender"), help)
            Log(f"{data.get('sender').name} has requested help.")
        else:
            bot.message(data.get("sender"), f"[{data.get('message')}] is an invalid command. Please type `message Banker = help` for a list of commands.")
            Log(f"{data.get('sender').name} has used an invalid command [{data.get('message')}].")

def OnBotConnect(bot):
    while(1):
        bot.ping()
        storage.save()
        time.sleep(10)

if __name__ == "__main__":
    bot = MuckletBot.Bot(TOKEN = "TOKEN", ORIGIN = "https://wolfery.com", HOST = "wss://api.wolfery.com", VERBOSE = False)
    bot.onMessage = OnBotMessage
    bot.onOpen = OnBotConnect
    bot.start()
  1. new bot library. again if intrested:
import websocket, json, threading, time, hashlib, hmac, codecs

PROMISE_TIMEOUT = "Promise Timed Out"
PROMISE_INVALID_REQUEST = "Invalid Promise Request"
PROMISE_TIMEOUT_VALUE = 10

def Error(promise, error): raise Exception(error.get("message"))

class Promise:
    def __init__(self, Manager, id): self.id = id; self.manager = Manager; self.value = None
    def join(self): self.value = self.manager.awaitResponse(self.id); return self
    def kill(self): self.manager.kill(self.id)
    def __str__(self): return f"Promise object [{self.id}:{self.manager.awaiting[self.id]}]"
    def error(self, func): func(self, self.value.get("error")) if self.value.get("error") is not None else None; return self

class PromiseManager:
    def __init__(self, ws): self.ws = ws; self.awaiting, self.itterID = {}, 0
    def __call__(self, method, params = None):
        self.itterID += 1
        id = self.itterID
        self.awaiting[id] = {'timeset': time.time(), 'response': None}
        self.send(method, id, params)
        return Promise(self, id)
    def awaitResponse(self, id):
        if self.awaiting.get(id) is None: raise Exception(PROMISE_INVALID_REQUEST)
        while self.awaiting[id]['response'] is None:
            if time.time() - self.awaiting[id]['timeset'] > PROMISE_TIMEOUT_VALUE: raise Exception(PROMISE_TIMEOUT)
        return self.awaiting[id]['response']
    def clean(self):
        for id in self.awaiting:
            if self.awaiting[id]['response'] is not None: del self.awaiting[id]
    def kill(self, id): del self.awaiting[id]
    def send(self, method, id = 0, params = None):
        request = {"id":id,"method":method,"params":params}
        msg = json.dumps(request)
        self.ws.send(msg) 
    def clear(self): self.awaiting = {}; self.itterID = 0

class Character:
    def __init__(self, bot, name = None, id = None): self.bot, self.name, self.id = bot, name, id

class Bot:
    def __init__(self, TOKEN:str = None, USERNAME:str = None, PASSWORD:str = None, BOTFIRSTNAME:str = None, BOTLASTNAME:str = None, HOST:str = 'wss://api.test.mucklet.com', ORIGIN:str = 'https://test.mucklet.com', VERBOSE:bool = False):
        self.ws = websocket.WebSocketApp(HOST,on_message = self._on_message,on_error = self._on_error,on_close = self._on_close,on_open = self._on_open)
        self.promise = PromiseManager(self.ws)
        self.TOKEN = TOKEN
        if self.TOKEN is not None:
            pass
        elif USERNAME is not None and PASSWORD is not None and BOTFIRSTNAME is not None and BOTLASTNAME is not None:
            self.BOTFIRSTNAME, self.BOTLASTNAME, self.USERNAME = BOTFIRSTNAME, BOTLASTNAME, USERNAME
            m = hashlib.sha256(); m.update(bytes(PASSWORD,'UTF-8'))
            self.PASS = codecs.encode(codecs.decode(m.hexdigest(), 'hex'), 'base64').decode()[:-1]
            self.HASH = codecs.encode(codecs.decode(hmac.new(bytes("TheStoryStartsHere",'UTF-8'),msg=bytes(PASSWORD,'UTF-8'), digestmod = hashlib.sha256).hexdigest(), 'hex'), 'base64').decode()[:-1]
        else: raise Exception("Invalid amount of login details provided. Must provide either a [token] or a [username, password, botfirstname, botlastname]")
        self.HOST, self.ORIGIN, self.VERBOSE = HOST, ORIGIN, VERBOSE
        self.onMessage, self.onError, self.onClose, self.onOpen = None, None, None, None
        self.CID, self.RID = None, None
    def start(self): self.ws.run_forever(origin=self.ORIGIN)
    def _on_message(self, ws, message):
        message = json.loads(message)
        if self.VERBOSE: print(json.dumps(message, indent=4, sort_keys=True))
        if message.get("id") is not None:
            if self.promise.awaiting.get(message.get("id")) is not None:
                self.promise.awaiting[message.get("id")]['response'] = message
                return
        if message.get("data") is None: return
        data = {
            "data": message.get("data"),
            "type": message.get("data").get("type"),
            "sender": Character(self, message.get("data").get("char").get("name") + " " + message.get("data").get("char").get("surname"), message.get("data").get("char").get("id")) if message.get("data").get("char") is not None else None,
            "reciver": Character(self, message.get("data").get("target").get("name")  + " " + message.get("data").get("char").get("surname"), message.get("data").get("target").get("id")) if message.get("data").get("target") is not None else None,
            "message": message.get("data").get("msg"),}
        if data.get("sender") is not None: 
            if data.get("sender").id == self.CID: return
        if message.get("event") is not None:
            if message.get("event").split(".")[2] != self.CID: return
        if self.onMessage is not None: 
            threading.Thread(target=self.onMessage, args=(self, data)).start()
    def _on_error(self, ws, error, message = None, code = None):print("error:", error)  
    def _on_close(self, ws, a = None, b = None):print("### closed ###", a, b)
    def _on_open(self, ws):threading.Thread(target=self.on_open, args=(ws,)).start()
    def on_open(self, ws):
        print("### open ###")
        if self.TOKEN is not None: value = self.promise("auth.auth.authenticateBot", {"token": self.TOKEN}).join().error(Error).value
        else: value = self.promise("auth.auth.login", {"name": self.USERNAME, "hash": self.HASH}).join().error(Error).value
        if self.TOKEN is not None: value = self.promise("call.core.getBot").join().error(Error).value
        else: value = self.promise("call.core.getPlayer").join().error(Error).value
        self.RID = value.get("result").get("rid")
        if self.TOKEN is not None: self.CID = self.RID.split('.')[-1]
        if self.TOKEN is None:
            value = self.promise("get."+self.RID).join().error(Error).value
            result = value.get("result"); models = result.get("models")
            for CID, data in models.items():
                if data.get("name") == self.BOTFIRSTNAME and data.get("surname") == self.BOTLASTNAME: self.CID = data.get("id")
        value = self.promise("call."+self.RID+".controlChar",params={'charId':self.CID}).join().error(lambda promise, error: None).value
        self.wake().join().error(lambda promise, error: None)
        value = self.promise("subscribe."+self.RID).join().error(Error).value
        if self.onOpen is not None: self.onOpen(self)
    def sleep(self): return self.promise("call.core.char."+self.CID+".ctrl.sleep")
    def wake(self): return self.promise("call.core.char."+self.CID+".ctrl.wakeup")
    def say(self, msg): return self.promise("call.core.char."+self.CID+".ctrl.say",params={'msg':msg})
    def pose(self, msg): return self.promise("call.core.char."+self.CID+".ctrl.pose",params={'msg':msg})
    def whisper(self, target, msg, pose='whispers'): return self.promise("call.core.char."+self.CID+".ctrl.whisper",params={'msg':msg,'charId':target.id if type(target) == Character else target,'pose':pose})
    def address(self, target, msg, pose='addreses'): return self.promise("call.core.char."+self.CID+".ctrl.address",params={'msg':msg,'charId':target.id if type(target) == Character else target,'pose':pose})
    def message(self, target, msg): return self.promise("call.core.char."+self.CID+".ctrl.message",params={'msg':msg,'charId':target.id if type(target) == Character else target})
    def ping(self): return self.promise("call.core.char."+self.CID+".ctrl.ping")
    def getChar(self, name): return self.promise("call."+self.RID+".getChar", params={"charName": name})
    def stop(self): self.ws.close()

Yes I know the code is absolutly messy as hell but I know whats going on and that’s all that matters ^w^