aboutsummaryrefslogtreecommitdiff
path: root/geldschieberbot.py
diff options
context:
space:
mode:
Diffstat (limited to 'geldschieberbot.py')
-rw-r--r--geldschieberbot.py153
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()
-