diff options
Diffstat (limited to 'geldschieberbot.py')
| -rw-r--r-- | geldschieberbot.py | 153 |
1 files changed, 97 insertions, 56 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py index 3791bd1..33fee20 100644 --- a/geldschieberbot.py +++ b/geldschieberbot.py @@ -7,7 +7,6 @@ import subprocess import sys import tanken - """Path where our data is stored persistent on disk""" state_file = os.environ["GSB_STATE_FILE"] @@ -21,12 +20,12 @@ else: scheduled_cmds - dict associating names to cmds, their schedule, and the last execution changes - dict associating users with their changes""" state = { - "balance" : {}, - "name2num" : {}, - "num2name" : {}, - "cars" : {}, - "scheduled_cmds" : {}, - "changes" : {}, + "balance": {}, + "name2num": {}, + "num2name": {}, + "cars": {}, + "scheduled_cmds": {}, + "changes": {}, } # check if a legacy file layout is present @@ -45,7 +44,7 @@ else: for num in state["changes"]: name = state["num2name"][num] state["changes"][name] = state["changes"][num] - del(state["changes"][num]) + del (state["changes"][num]) balance = state["balance"] name2num = state["name2num"] @@ -58,16 +57,14 @@ group_id = os.environ["GSB_GROUP_ID"] send_cmd = os.environ["GSB_SEND_CMD"] group_send_cmd = send_cmd + group_id - """Run without changing the stored state""" dry_run = False - """Run without sending messages""" quiet = False - """Should changes be recorded""" record_changes = True + def record(recipient, donor, amount): """Apply changes to the balance""" @@ -78,6 +75,7 @@ def record(recipient, donor, amount): balance[donor][recipient] += amount balance[recipient][donor] -= amount + def to_cent(euro): if '.' in euro: euro = euro.split('.') @@ -98,19 +96,22 @@ def to_cent(euro): raise ValueError return amount + def to_euro(cents): return f"{cents/100:.2f}" + def send(msg): if not quiet: subprocess.run(send_cmd.split(' '), input=msg.encode()) + def create_summary(user): summary = "" cars_summary = "" total = 0 cars_total = 0 - p_balances = balance[user] # failes if user is not in balance + p_balances = balance[user] # failes if user is not in balance for person in p_balances: amount = p_balances[person] if amount == 0: @@ -133,7 +134,8 @@ def create_summary(user): cars_summary += f'\tLiability: {to_euro(cars_total)}' ret_summary += f'\n\tCars:\n{cars_summary}' - return ret_summary + return ret_summary + def create_total_summary(): summary = "Summary:" @@ -150,12 +152,14 @@ def create_total_summary(): summary += f'\nCars:{cars_summary}' return summary + def create_members(): r = "" for m in name2num: r += m + ": " + name2num[m] + "\n" return r + def add_to_balance(name): nb = {} for m in balance: @@ -163,6 +167,7 @@ def add_to_balance(name): nb[m] = 0 balance[name] = nb + def create_help(): return """ Usage: send a message starting with '!' followed by a command @@ -201,8 +206,10 @@ cancel name - stop repeating cmd Happy Geldschieben! """ + cmds = {} + def register(sender, args, msg): if len(args) != 2: return None, f'not in form "{args[0]} name"' @@ -229,9 +236,11 @@ def register(sender, args, msg): changes[name] = [] return f"Happy geldschiebing {name}!", None + cmds["reg"] = register cmds["register"] = register + def summary(sender, args, msg): if len(args) == 1: return create_total_summary(), None @@ -245,21 +254,27 @@ def summary(sender, args, msg): err = f'name "{name}" not registered' return msg, err + cmds["sum"] = summary cmds["summary"] = summary + def list_users(sender, args, msg): return create_members(), None + cmds["ls"] = list_users cmds["list"] = list_users + def usage(sender, args, msg): return create_help(), None + cmds["help"] = usage cmds["usage"] = usage + def split(sender, args, msg): if not sender in num2name: return None, 'you must register first' @@ -283,7 +298,7 @@ def split(sender, args, msg): # persons + sender npersons = len(persons) + 1 - amount_per_person = int(amount/npersons) + amount_per_person = int(amount / npersons) if sender in num2name: recipient = num2name[sender] @@ -309,9 +324,11 @@ def split(sender, args, msg): output += create_summary(recipient) return output, None + cmds["split"] = split cmds["teil"] = split + def transaction(sender, args, msg): if len(args) != 3: return None, f'not in form "{args[0]} amount recipient"' @@ -346,17 +363,18 @@ def transaction(sender, args, msg): p_balance = balance[sender][recipient] - output = ("New Balance: {} {} {} {}\n".format(sender, - ("->" if p_balance > 0 else "<-"), - to_euro(abs(p_balance)), - recipient)) + output = ("New Balance: {} {} {} {}\n".format( + sender, ("->" if p_balance > 0 else "<-"), to_euro(abs(p_balance)), + recipient)) return output, None + cmds["schieb"] = transaction cmds["gib"] = transaction cmds["zieh"] = transaction cmds["nimm"] = transaction + def transfer(sender, args, msg): if len(args) < 4: return None, f'not in form "{args[0]} amount source destination"' @@ -403,7 +421,7 @@ def transfer(sender, args, msg): # Sender -> X Destination change.append((sender, destination, amount_cent)) - ret, err = transaction(source, ["!zieh", destination, amount_raw], "") + ret, err = transaction(source, ["!zieh", destination, amount_raw], "") if err: output += err + "\nThe balance may be in a inconsistent state please take care manually" return output, None @@ -418,8 +436,10 @@ def transfer(sender, args, msg): return output, None + cmds["transfer"] = transfer + def cars(sender, args, msg): # list cars if len(args) < 2 or args[1] in ["ls", "list"]: @@ -427,7 +447,7 @@ def cars(sender, args, msg): return "No cars registered yet.", None ret_msg = "" - + if len(args) > 2: cars_to_list = args[2:] else: @@ -501,7 +521,7 @@ def cars(sender, args, msg): proportion = -1 * (amount / total_available_charge) _, err = transaction(sender, f"!gib {car} {amount_euro}".split(), "") - assert(err is None) + assert (err is None) output += f"{sender_name} payed {amount_euro}\n" # transfer money @@ -512,10 +532,12 @@ def cars(sender, args, msg): to_move = int(_amount * proportion) to_move_euro = to_euro(to_move) - ret, err = transfer(sender, ["transfer", to_move_euro, car, person], "") - assert(err is None) + ret, err = transfer(sender, + ["transfer", to_move_euro, car, person], "") + assert (err is None) - output += "Transfer {} from {} to {}\n".format(to_move_euro, person, sender_name) + output += "Transfer {} from {} to {}\n".format( + to_move_euro, person, sender_name) output += "New Balances:\n" output += create_summary(sender_name) + "\n" @@ -529,11 +551,14 @@ def cars(sender, args, msg): else: return None, 'unknown car subcommand "{}".'.format(args[1]) + cmds["cars"] = cars + def _tanken(sender, args, msg): if len(args) < 2: - return None, 'not in form "{} amount [person] [car] [info]"'.format(args[0]) + return None, 'not in form "{} amount [person] [car] [info]"'.format( + args[0]) try: amount = to_cent(args[1]) except: @@ -566,7 +591,9 @@ def _tanken(sender, args, msg): output = "" change = [args] for pname, values in parts.items(): - output += pname + ": {}km = fuel: {}, service charge: {}\n".format(values["distance"], to_euro(values["cost"]), to_euro(values["service_charge"])) + output += pname + ": {}km = fuel: {}, service charge: {}\n".format( + values["distance"], to_euro(values["cost"]), + to_euro(values["service_charge"])) # record service charges if pname not in name2num: output += pname + " not known." @@ -574,7 +601,8 @@ def _tanken(sender, args, msg): person_to_charge = pname if pname not in name2num: person_to_charge = recipient - output += " {} held accountable for service charge.".format(recipient) + output += " {} held accountable for service charge.".format( + recipient) record(car, person_to_charge, values["service_charge"]) change.append([car, person_to_charge, values["service_charge"]]) @@ -599,8 +627,10 @@ def _tanken(sender, args, msg): output += create_summary(car) return output, None + cmds["tanken"] = _tanken + def fuck(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -630,9 +660,9 @@ def fuck(sender, args, msg): for change in last_changes: if not change[0] in cmds: output += "{} {} {} {}\n".format(change[0], - ("->" if change[2] < 0 else "<-"), - to_euro(abs(change[2])), - change[1]) + ("->" if change[2] < 0 else "<-"), + to_euro(abs(change[2])), + change[1]) record(change[1], change[0], change[2]) for change in last_changes: @@ -642,14 +672,16 @@ def fuck(sender, args, msg): if err: output += "ERROR: " + err else: - output += ret + output += ret return output, None + cmds["fuck"] = fuck cmds["rewind"] = fuck cmds["undo"] = fuck + def list_changes(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -663,7 +695,6 @@ def list_changes(sender, args, msg): except ValueError: return None, 'the amount of changes to list must be a number' - nchanges = len(changes[sender_name]) if nchanges == 0: return "Nothing to list", None @@ -696,17 +727,18 @@ def list_changes(sender, args, msg): msg += f'\t{" ".join(change[0])}\n' for sender, recipient, amount in change[1:]: msg += "\t{} {} {} {}\n".format(sender, - ("->" if amount < 0 else "<-"), - to_euro(abs(amount)), - recipient) + ("->" if amount < 0 else "<-"), + to_euro(abs(amount)), recipient) # prepend message header because we want to know how much changes we actually listed msg = f'Changes from {sender_name} {first_to_list + 1}-{i + 1}\n' + msg return msg, None + cmds["list-changes"] = list_changes + def schedule(sender, args, msg): if not sender in num2name: return None, "you must register first" @@ -720,7 +752,8 @@ def schedule(sender, args, msg): cmd = args[2:] if name in scheduled_cmds: - return None, 'there is already a scheduled command named "{}"'.format(name) + return None, 'there is already a scheduled command named "{}"'.format( + name) # Test the command global dry_run @@ -733,16 +766,19 @@ def schedule(sender, args, msg): if err: return None, 'the command "{}" failed and will not be recorded' - scheduled_cmd = {"schedule": args[0][1:], - "last_time": None, - "sender": sender, - "cmd": cmd} + scheduled_cmd = { + "schedule": args[0][1:], + "last_time": None, + "sender": sender, + "cmd": cmd + } scheduled_cmds[name] = scheduled_cmd - output = 'Recorded the {} command "{}" as "{}"\n'.format(args[0][1:], ' '.join(cmd), name) + output = 'Recorded the {} command "{}" as "{}"\n'.format( + args[0][1:], ' '.join(cmd), name) - output += "Running {} command {} for {} initially\n".format(scheduled_cmd["schedule"], - name, sender_name) + output += "Running {} command {} for {} initially\n".format( + scheduled_cmd["schedule"], name, sender_name) ret, err = cmds[cmd[0]](sender, cmd, "") if err: @@ -757,10 +793,12 @@ def schedule(sender, args, msg): return output, None + cmds["weekly"] = schedule cmds["monthly"] = schedule cmds["yearly"] = schedule + def cancel(sender, args, msg): cmd_name = args[1] if not cmd_name in scheduled_cmds: @@ -770,11 +808,13 @@ def cancel(sender, args, msg): if not cmd["sender"] == sender: return None, 'only the original creator can cancel this command' - del(scheduled_cmds[cmd_name]) + del (scheduled_cmds[cmd_name]) return 'Cancelled the {} cmd "{}"'.format(cmd["schedule"], cmd_name), None + cmds["cancel"] = cancel + def main(): if len(sys.argv) > 1 and sys.argv[1] in ["-d", "--dry-run"]: global dry_run @@ -790,11 +830,13 @@ def main(): continue sender_number = message["source"] - if not "dataMessage" in message or not message["dataMessage"] or not message["dataMessage"]["message"]: + if not "dataMessage" in message or not message[ + "dataMessage"] or not message["dataMessage"]["message"]: continue else: message = message["dataMessage"] - if message["groupInfo"] and message["groupInfo"]["groupId"] != group_id: + if message["groupInfo"] and message["groupInfo"][ + "groupId"] != group_id: continue body = [l.strip() for l in message["message"].lower().splitlines()] @@ -832,24 +874,24 @@ def main(): d = last_time while True: if cmd["schedule"] == "yearly": - d = date(d.year+1, d.month, d.day) + d = date(d.year + 1, d.month, d.day) elif cmd["schedule"] == "monthly": if d.day > 28: d = date(d.year, d.month, 28) if d.month == 12: - d = date(d.year+1, 1, d.day) + d = date(d.year + 1, 1, d.day) else: - d = date(d.year, d.month+1, d.day) + d = date(d.year, d.month + 1, d.day) else: d = d + timedelta(7) if d <= now: - send("Running {} command {} for {} triggered on {}\n".format(cmd["schedule"], - name, - num2name[cmd["sender"]], - d.isoformat())) + send("Running {} command {} for {} triggered on {}\n". + format(cmd["schedule"], name, num2name[cmd["sender"]], + d.isoformat())) - ret, err = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"], "") + ret, err = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"], + "") if err: send("ERROR: " + err) @@ -860,10 +902,9 @@ def main(): else: break - with open(state_file, "w") as f: json.dump(state, f) + if __name__ == "__main__": main() - |
