aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2021-11-18 11:25:50 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2021-11-18 11:25:50 +0100
commitd5119809549c3a8255c1677a98451e88115f674e (patch)
tree6540474161b19e5556bfa7d024179df1f7271c99
parentb2f0f481eee4f9be48e1212bd27a8671c2e9ceca (diff)
downloadgeldschieberbot-d5119809549c3a8255c1677a98451e88115f674e.tar.gz
geldschieberbot-d5119809549c3a8255c1677a98451e88115f674e.zip
geldschieberbot: add export-state cmd
Add new command sending the state as an attachment. To allow this we refactor the return type of the command functions intead of returning a tuple(msg, err) they now return a dictionary. This is more flexible the members are position independent and it allows us to add arbitraty new return values like 'attachment'. If 'attachment' is present in the result of a command it will be attached to the message send.
-rw-r--r--geldschieberbot.py305
1 files changed, 167 insertions, 138 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py
index fb168d4..68e6e1d 100644
--- a/geldschieberbot.py
+++ b/geldschieberbot.py
@@ -105,13 +105,15 @@ def to_euro(cents):
return f"{cents/100:.2f}"
-def send(msg):
+def send(msg, attachment=None, cmd=send_cmd):
if not quiet:
- subprocess.run(send_cmd.split(' '), input=msg.encode(), check=False)
+ if attachment:
+ cmd += f'-a {attachment}'
+ subprocess.run(cmd.split(' '), input=msg.encode(), check=False)
def create_summary(user):
- summary = ""
+ msg = ''
cars_summary = ""
total = 0
cars_total = 0
@@ -125,14 +127,14 @@ def create_summary(user):
cars_summary += f'\t{"<-" if amount < 0 else "->"} {person} {to_euro(abs(amount))}\n'
else:
total -= amount
- summary += f'\t{"<-" if amount < 0 else "->"} {person} {to_euro(abs(amount))}\n'
+ msg += f'\t{"<-" if amount < 0 else "->"} {person} {to_euro(abs(amount))}\n'
- if not summary:
- summary = "\tAll fine :)"
+ if not msg:
+ msg = '\tAll fine :)'
else:
- summary += f"\tBalance: {to_euro(total)}"
+ msg += f'\tBalance: {to_euro(total)}'
- ret_summary = f'{user}:\n{summary}'
+ ret_summary = f'{user}:\n{msg}'
if cars_summary:
cars_summary += f'\tLiability: {to_euro(cars_total)}'
@@ -141,23 +143,23 @@ def create_summary(user):
return ret_summary
-def create_total_summary():
- summary = "Summary:"
+def create_total_summary() -> str:
+ msg = 'Summary:'
- cars_summary = ""
+ cars_summary = ''
for person in balance:
p_summary = create_summary(person)
if person in available_cars:
cars_summary += f'\n{p_summary}'
else:
- summary += f'\n{p_summary}'
+ msg += f'\n{p_summary}'
if cars_summary:
- summary += f'\nCars:{cars_summary}'
- return summary
+ msg += f'\nCars:{cars_summary}'
+ return msg
-def create_members():
+def create_members() -> str:
r = ""
for m in name2num:
r += m + ": " + name2num[m] + "\n"
@@ -210,6 +212,8 @@ tanken amount [person] [car] [info] - calculate fuel costs, service charge and a
fuck [change] - rewind last or specific change
list-changes [n] [first] - list the last n changes starting at first
+export-state - send the state file
+
weekly name cmd - repeat cmd each week
monthly name cmd - repeat cmd each month
yearly name cmd - repeat cmd each year
@@ -222,22 +226,22 @@ Happy Geldschieben!
cmds = {}
-def register(sender, args, msg):
+def register(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if len(args) != 2:
- return None, f'not in form "{args[0]} name"'
+ return {'err': f'not in form "{args[0]} name"'}
name = args[1]
try:
to_cent(name)
- return None, "pure numerical names are not allowed"
+ return {'err': 'pure numerical names are not allowed'}
except (ValueError, TypeError):
pass
if name in name2num:
- return None, f"{name} already registered"
+ return {'err': f'{name} already registered'}
if sender in num2name:
- return None, "you are already registered"
+ return {'err': 'you are already registered'}
num2name[sender] = name
name2num[name] = sender
@@ -246,67 +250,66 @@ def register(sender, args, msg):
# add changes list
changes[name] = []
- return f"Happy geldschiebing {name}!", None
+ return {'msg': f'Happy geldschiebing {name}!'}
cmds["reg"] = register
cmds["register"] = register
-def summary(sender, args, msg): # pylint: disable=unused-argument
+def summary(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if len(args) == 1:
if not sender in num2name:
- return None, "You must register first to print your summary"
+ return {'err': 'You must register first to print your summary'}
name = num2name[sender]
- return f"Summary:\n{create_summary(name)}", None
+ return {'msg': f'Summary:\n{create_summary(name)}'}
- err = None
msg = "Summary:\n"
for name in args[1:]:
if name in name2num or name in available_cars:
msg += create_summary(name) + "\n"
else:
- err = f'name "{name}" not registered'
- return msg, err
+ return {'err': f'name "{name}" not registered'}
+ return {'msg': msg}
cmds["sum"] = summary
cmds["summary"] = summary
-def full_summary(sender, args, msg): # pylint: disable=unused-argument
+def full_summary(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if len(args) == 1:
- return create_total_summary(), None
+ return {'msg': create_total_summary()}
else:
- return None, f"{args[0][1:]} takes no arguments"
+ return {'err': f'{args[0][1:]} takes no arguments'}
cmds["full-sum"] = full_summary
cmds["full-summary"] = full_summary
-def list_users(sender, args, msg): # pylint: disable=unused-argument
- return create_members(), None
+def list_users(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
+ return {'msg': create_members()}
cmds["ls"] = list_users
cmds["list"] = list_users
-def usage(sender, args, msg): # pylint: disable=unused-argument
- return create_help(), None
+def usage(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
+ return {'msg': create_help()}
cmds["help"] = usage
cmds["usage"] = usage
-def split(sender, args, msg):
+def split(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if not sender in num2name:
- return None, 'you must register first'
+ return {'err': 'you must register first'}
if len(args) < 3:
- return None, f'not in form "{args[0]} amount [name]+"'
+ return {'err': f'not in form "{args[0]} amount [name]+"'}
try:
amount = to_cent(args[1])
@@ -318,9 +321,9 @@ def split(sender, args, msg):
amount = to_cent(args[2])
persons = [args[1]]
except (ValueError, TypeError):
- return None, "amount must be a positive number"
+ return {'err': 'amount must be a positive number'}
else:
- return None, "amount must be a positive number"
+ return {'err': 'amount must be a positive number'}
# persons + sender
npersons = len(persons) + 1
@@ -336,7 +339,8 @@ def split(sender, args, msg):
for p in persons:
if p in name2num:
if p == recipient:
- output += f"{p}, you will be charged multiple times. This may not be what you want\n"
+ output += (f'{p}, you will be charged multiple times. '
+ 'This may not be what you want\n')
else:
record(recipient, p, amount_per_person)
change.append([recipient, p, amount_per_person])
@@ -348,20 +352,20 @@ def split(sender, args, msg):
output += "New Balance:\n"
output += create_summary(recipient)
- return output, None
+ return {'msg': output}
cmds["split"] = split
cmds["teil"] = split
-def transaction(sender, args, msg):
+def transaction(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if len(args) != 3:
- return None, f'not in form "{args[0]} amount recipient"'
+ return {'err': f'not in form "{args[0]} amount recipient"'}
if not sender in balance:
if sender not in num2name:
- return None, 'you must register first'
+ return {'err': 'you must register first'}
sender = num2name[sender]
if args[1] in balance:
@@ -369,15 +373,15 @@ def transaction(sender, args, msg):
elif args[2] in balance:
amount, recipient = args[1:3]
else:
- return None, 'recipient not known'
+ return {'err': 'recipient not known'}
if sender == recipient:
- return None, 'you can not transfer money to or from yourself'
+ return {'err': 'you can not transfer money to or from yourself'}
try:
amount = to_cent(amount)
except (ValueError, TypeError):
- return None, "amount must be a positive number"
+ return {'err': 'amount must be a positive number'}
if args[0] in ["!zieh", "!nimm"]:
amount *= -1
@@ -392,7 +396,7 @@ def transaction(sender, args, msg):
output = ("New Balance: {} {} {} {}\n".format(
sender, ("->" if p_balance > 0 else "<-"), to_euro(abs(p_balance)),
recipient))
- return output, None
+ return {'msg': output}
cmds["schieb"] = transaction
@@ -401,12 +405,12 @@ cmds["zieh"] = transaction
cmds["nimm"] = transaction
-def transfer(sender, args, msg):
+def transfer(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if len(args) < 4:
- return None, f'not in form "{args[0]} amount source destination"'
+ return {'err': f'not in form "{args[0]} amount source destination"'}
if not sender in num2name:
- return None, 'you must register first'
+ return {'err': 'you must register first'}
sender = num2name[sender]
@@ -414,14 +418,14 @@ def transfer(sender, args, msg):
amount_raw = args[1]
amount_cent = to_cent(amount_raw)
except (ValueError, TypeError):
- return None, "amount must be a positive number"
+ return {'err': 'amount must be a positive number'}
source, destination = args[2:4]
if source not in balance:
- return None, f'source "{source}" not known'
+ return {'err': f'source "{source}" not known'}
if destination not in balance:
- return None, f'destination "{destination}" not known'
+ return {'err': f'destination "{destination}" not known'}
output = ""
global record_changes
@@ -429,30 +433,32 @@ def transfer(sender, args, msg):
record_changes = False
change = [args]
- ret, err = transaction(sender, ["!zieh", source, amount_raw], "")
- if err:
+ ret = transaction(sender, ["!zieh", source, amount_raw], "")
+ if 'err' in ret:
# No changes yet we can fail
- return None, err
+ return {'err': ret['err']}
- output += ret
+ output += ret['msg']
# Sender <- X Source
change.append((sender, source, -amount_cent))
- ret, err = transaction(sender, ["!schieb", destination, amount_raw], "")
+ ret = transaction(sender, ["!schieb", destination, amount_raw], "")
+ err = ret.get('err', None)
if err:
output += err + "\nThe balance may be in a inconsistent state please take care manually"
- return output, None
+ return {'msg': output}
- output += ret
+ output += ret['msg']
# Sender -> X Destination
change.append((sender, destination, amount_cent))
- ret, err = transaction(source, ["!zieh", destination, amount_raw], "")
+ ret = transaction(source, ["!zieh", destination, amount_raw], "")
+ err = ret.get('err', None)
if err:
output += err + "\nThe balance may be in a inconsistent state please take care manually"
- return output, None
+ return {'msg': output}
- output += ret
+ output += ret['msg']
# Destination -> X Source
change.append((destination, source, amount_cent))
@@ -460,17 +466,17 @@ def transfer(sender, args, msg):
if err is None and record_changes and not dry_run:
changes[sender].append(change)
- return output, None
+ return {'msg': output}
cmds["transfer"] = transfer
-def cars(sender, args, msg):
+def cars(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
# list cars
if len(args) < 2 or args[1] in ["ls", "list"]:
if len(available_cars) == 0:
- return "No cars registered yet.", None
+ return {'msg': 'No cars registered yet.'}
ret_msg = ""
@@ -484,63 +490,71 @@ def cars(sender, args, msg):
ret_msg += f"{car} - service charge {available_cars[car]}ct/km\n"
ret_msg += create_summary(car) + "\n"
else:
- return None, f'"{car}" is no available car\n'
+ return {'err': f'"{car}" is no available car\n'}
- return ret_msg[:-1], None
+ return {'msg': ret_msg[:-1]}
# add car
if args[1] in ["add", "new"]:
if len(args) < 4:
- return None, f'not in form "{args[0]} {args[1]} car-name service-charge"'
+ return {
+ 'err':
+ f'not in form "{args[0]} {args[1]} car-name service-charge"'
+ }
car = args[2]
if car in available_cars:
- return None, f'"{car}" already registered'
+ return {'err': f'"{car}" already registered'}
if car in balance:
- return None, f'A user named "{car}" already exists. Please use a different name for this car'
+ return {
+ 'err':
+ f'A user named "{car}" already exists. Please use a different name for this car'
+ }
try:
service_charge = to_cent(args[3])
except (ValueError, TypeError):
- return None, "service-charge must be a positive number"
+ return {'err': 'service-charge must be a positive number'}
available_cars[car] = service_charge
add_to_balance(car)
- return f'added "{car}" as an available car', None
+ return {'msg': f'added "{car}" as an available car'}
# remove car
if args[1] in ["rm", "remove"]:
if len(args) < 3:
- return None, f'not in form "{args[0]} {args[1]} car-name"'
+ return {'err': f'not in form "{args[0]} {args[1]} car-name"'}
car = args[2]
if car not in available_cars:
- return None, f'A car with the name "{car}" does not exists'
+ return {'err': f'A car with the name "{car}" does not exists'}
del available_cars[car]
remove_from_balance(car)
- return f'removed "{car}" from the available cars', None
+ return {'msg': f'removed "{car}" from the available cars'}
# pay bill
if args[1] in ["pay"]:
if len(args) < 4:
- return None, f'not in form "{args[0]} {args[1]} car-name amount"'
+ return {
+ 'err': f'not in form "{args[0]} {args[1]} car-name amount"'
+ }
if not sender in num2name:
- return None, "you must register first"
+ return {'err': 'you must register first'}
sender_name = num2name[sender]
car = args[2]
if car not in available_cars:
- return None, f'car "{car}" not known'
+ return {'err': f'car "{car}" not known'}
try:
amount = to_cent(args[3])
amount_euro = to_euro(amount)
except (ValueError, TypeError):
- return None, "amount must be a positive number"
+ return {'err': 'amount must be a positive number'}
output = ""
@@ -561,8 +575,8 @@ def cars(sender, args, msg):
if amount < total_available_charge:
proportion = -1 * (amount / total_available_charge)
- _, err = transaction(sender, f"!gib {car} {amount_euro}".split(), "")
- assert err is None
+ ret = transaction(sender, f'!gib {car} {amount_euro}'.split(), '')
+ assert 'err' not in ret
output += f"{sender_name} payed {amount_euro}\n"
# transfer money
@@ -573,9 +587,8 @@ 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 = transfer(sender, ['transfer', to_move_euro, car, person], '')
+ assert 'err' not in ret
output += "Transfer {} from {} to {}\n".format(
to_move_euro, person, sender_name)
@@ -588,21 +601,21 @@ def cars(sender, args, msg):
if record_changes and not dry_run:
changes[sender_name].append(change)
- return output, None
+ return {'msg': output}
- return None, f'unknown car subcommand "{args[1]}".'
+ return {'err': f'unknown car subcommand "{args[1]}".'}
cmds["cars"] = cars
-def _tanken(sender, args, msg):
+def _tanken(sender, args, msg) -> dict[str, str]:
if len(args) < 2:
- return None, f'not in form "{args[0]} amount [person] [car] [info]"'
+ return {'err': f'not in form "{args[0]} amount [person] [car] [info]"'}
try:
amount = to_cent(args[1])
except (ValueError, TypeError):
- return None, "amount must be a number"
+ return {'err': 'amount must be a number'}
# find recipient
if len(args) > 2 and args[2] in name2num:
@@ -610,7 +623,7 @@ def _tanken(sender, args, msg):
elif sender in num2name:
recipient = num2name[sender]
else:
- return None, "recipient unknown"
+ return {'err': 'recipient unknown'}
# find car
car = None
@@ -626,7 +639,7 @@ def _tanken(sender, args, msg):
parts, err = tanken.tanken(msg[1:], amount, service_charge)
if err:
- return None, err
+ return {'err': err}
output = ""
change = [args]
@@ -664,31 +677,31 @@ def _tanken(sender, args, msg):
if car:
output += "\nCar "
output += create_summary(car)
- return output, None
+ return {'msg': output}
cmds["tanken"] = _tanken
-def fuck(sender, args, msg):
+def fuck(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if not sender in num2name:
- return None, "you must register first"
+ return {'err': 'you must register first'}
name = num2name[sender]
nchanges = len(changes[name])
if nchanges == 0:
- return "Nothing to rewind", None
+ return {'msg': 'Nothing to rewind'}
change_to_rewind = -1
if len(args) >= 2:
try:
change_to_rewind = int(args[1]) - 1
except ValueError:
- return None, "change to rewind must be a number"
+ return {'err': 'change to rewind must be a number'}
if change_to_rewind > nchanges:
- return None, "change to rewind is bigger than there are changes"
+ return {'err': 'change to rewind is bigger than there are changes'}
# pop last item
last_changes = changes[name].pop(change_to_rewind)
@@ -706,14 +719,14 @@ def fuck(sender, args, msg):
for change in last_changes:
if change[0] in cmds:
- ret, err = cmds[change[0]](sender, change, "")
+ ret = cmds[change[0]](sender, change, "")
- if err:
- output += "ERROR: " + err
+ if 'err' in ret:
+ output += "ERROR: " + ret['err']
else:
- output += ret
+ output += ret['msg']
- return output, None
+ return {'msg': output}
cmds["fuck"] = fuck
@@ -721,9 +734,9 @@ cmds["rewind"] = fuck
cmds["undo"] = fuck
-def list_changes(sender, args, msg):
+def list_changes(sender, args, msg) -> dict[str, str]:
if not sender in num2name:
- return None, "you must register first"
+ return {'err': 'you must register first'}
sender_name = num2name[sender]
@@ -732,11 +745,11 @@ def list_changes(sender, args, msg):
try:
changes_to_list = int(args[1])
except ValueError:
- return None, 'the amount of changes to list must be a number'
+ return {'err': 'the amount of changes to list must be a number'}
nchanges = len(changes[sender_name])
if nchanges == 0:
- return "Nothing to list", None
+ return {'msg': 'Nothing to list'}
first_to_list = max(nchanges - changes_to_list, 0)
@@ -744,12 +757,16 @@ def list_changes(sender, args, msg):
try:
first_to_list = int(args[2]) - 1
except ValueError:
- return None, 'the first change to list must be a number'
+ return {'err': 'the first change to list must be a number'}
if first_to_list > nchanges:
- return None, 'the first change to list is bigger than there are changes'
+ return {
+ 'err':
+ 'the first change to list is bigger than there are changes'
+ }
msg = ""
+ i = 0
for i, change in enumerate(changes[sender_name]):
if i < first_to_list:
continue
@@ -770,37 +787,48 @@ def list_changes(sender, args, msg):
# 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
+ return {'msg': msg}
cmds["list-changes"] = list_changes
-def schedule(sender, args, msg):
+def export_state(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
if not sender in num2name:
- return None, "you must register first"
+ return {'err': 'you must register first'}
+
+ msg = f'State from {datetime.now().date().isoformat()}'
+ return {'msg': msg, 'attachment': state_file}
+
+
+cmds["export-state"] = export_state
+
+
+def schedule(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
+ if not sender in num2name:
+ return {'err': 'you must register first'}
sender_name = num2name[sender]
if len(args) < 3:
- return None, f'not in form "{args[0]} name cmd"'
+ return {'err': f'not in form "{args[0]} name cmd"'}
name = args[1]
cmd = args[2:]
if name in scheduled_cmds:
- return None, f'there is already a scheduled command named "{name}"'
+ return {'err': f'there is already a scheduled command named "{name}"'}
# Test the command
global dry_run
old_dry_run, dry_run = dry_run, True
- ret, err = cmds[cmd[0]](sender, cmd, "")
+ ret = cmds[cmd[0]](sender, cmd, '')
dry_run = old_dry_run
- if err:
- return None, 'the command "{}" failed and will not be recorded'
+ if 'err' in ret:
+ return {'err': 'the command "{}" failed and will not be recorded'}
scheduled_cmd = {
"schedule": args[0][1:],
@@ -816,18 +844,18 @@ def schedule(sender, args, msg):
output += "Running {} command {} for {} initially\n".format(
scheduled_cmd["schedule"], name, sender_name)
- ret, err = cmds[cmd[0]](sender, cmd, "")
- if err:
- output += "ERROR: " + err
+ ret = cmds[cmd[0]](sender, cmd, "")
+ if 'err' in ret:
+ output += 'ERROR: ' + ret['err']
else:
- output += ret
+ output += ret['msg']
changes[sender_name][0].append(["cancel", name])
now = datetime.now().date()
scheduled_cmd["last_time"] = now.isoformat()
- return output, None
+ return {'msg': output}
cmds["weekly"] = schedule
@@ -835,30 +863,30 @@ cmds["monthly"] = schedule
cmds["yearly"] = schedule
-def cancel(sender, args, msg):
+def cancel(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
cmd_name = args[1]
if not cmd_name in scheduled_cmds:
- return None, f'"{cmd_name}" is not a scheduled command'
+ return {'err': f'"{cmd_name}" is not a scheduled command'}
cmd = scheduled_cmds[cmd_name]
if not cmd["sender"] == sender:
- return None, 'only the original creator can cancel this command'
+ return {'err': 'only the original creator can cancel this command'}
del scheduled_cmds[cmd_name]
- return f'Cancelled the {cmd["schedule"]} cmd "{cmd_name}"', None
+ return {'msg': f'Cancelled the {cmd["schedule"]} cmd "{cmd_name}"'}
cmds["cancel"] = cancel
-def thanks(sender, args, msg):
+def thanks(sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
sender_name = num2name.get(sender, sender)
msg = f'You are welcome. It is a pleasure to work with you, {sender_name}.'
nick = None if len(args) == 1 else args[1]
if nick:
msg = f"{msg}\nBut don't call me {nick}."
- return msg, None
+ return {'msg': msg}
cmds["thanks"] = thanks
@@ -897,11 +925,13 @@ def main():
if args[0].startswith("!"):
cmd = args[0][1:]
if cmd in cmds:
- ret, err = cmds[cmd](sender_number, args, body)
- if err:
- send("ERROR: " + err)
+ ret = cmds[cmd](sender_number, args, body)
+ if 'err' in ret:
+ send(f'ERROR: {ret["err"]}')
else:
- send(ret)
+ if not 'msg' in ret:
+ print(ret)
+ send(ret['msg'], attachment=ret.get('attachment', None))
else:
send('ERROR: unknown cmd. Enter !help for a list of commands.')
@@ -938,13 +968,12 @@ def main():
format(cmd["schedule"], name, num2name[cmd["sender"]],
d.isoformat()))
- ret, err = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"],
- "")
+ ret = cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"], "")
- if err:
- send("ERROR: " + err)
+ if 'err' in ret:
+ send("ERROR: " + ret['err'])
else:
- send(ret)
+ send(ret['msg'])
cmd["last_time"] = d.isoformat()
else: