added support for Telegram and MQTT

This commit is contained in:
Steffen Vogel 2018-04-29 21:12:36 +02:00
parent d0b88bfda3
commit 7fbe368926
4 changed files with 124 additions and 72 deletions

View File

@ -3,9 +3,13 @@
## Setup
1. Install pushfin: `pip3 install pushfin`
2. Create configuration file at: `~/.pushfin.yaml`
2. Create configuration file at: `~/.pushfin.yaml`. Take a look at the [example configuration](https://github.com/stv0g/pushfin/blob/master/etc/pushfin.yaml).
3. Add pushfin to crontab: `crontab -e`:
```
12 * * * * pushfin
````
## Transaction fields for formatting
### MT940
@ -18,14 +22,13 @@ Common fields are listed below:
| :-- | :-- | :-- |
| `trx[status]` | 'D' | 'D' = Debit, 'C' = Credit |
| `trx[funds_code]` | None |
| `trx[amount]` | <-46.5 EUR> |
| `trx[id]` | 'NMSC' |
| `trx[customer_reference]` | None |
| `trx[bank_reference]` | None |
| `trx[extra_details]` | '' |
| `trx[currency]` | 'EUR' |
| `trx[date]` | Date(2018, 1, 2) |
| `trx[entry_date]` | Date(2018, 1, 2) |
| `trx[date]` | | Unix Timestamp |
| `trx[entry_date]` | | Unix Timestamp |
| `trx[transaction_code]` | '020' |
| `trx[posting_text]` | 'Überweisung' |
| `trx[prima_nota]` | '006200' |
@ -63,10 +66,10 @@ For ease formatting, we extended the standard MT940 fields with the following he
| `trx[date_fmt]` | '2018-04-28' | A formatted date of the `date` field |
| `trx[entry_date_ts]` | 1525007188 | A Unix timestamp of the `entry_date` field |
| `trx[entry_date_fmt]` | '2018-04-28' | A formatted date of the `entry_date` field |
| `trx[value]` | -20.42 | Just the amount of the transaction (see `amount`) |
| `trx[amout]` | -20.42 | Just the amount of the transaction (see `amount`) |
| `trx[dir]` | 'from'/'to' | |
| `trx[color]` | '#009933' | |
| `bal[value]` | '3.52' | |
| `bal[amount]` | '3.52' | |
| `bal[currency]` | 'EUR' | Current balance valuta |
| `bal[date]` | '2018-03-23' | Balance currency |
| `bal[date_fmt]` | | Date of last valuta |

View File

@ -13,12 +13,14 @@ import sys
import os.path
import logging
import yaml
import json
import time
import datetime
import urllib
import hashlib
import fints.client
import http.client
import paho.mqtt.publish as publish
logger = logging.getLogger('pushfin')
@ -28,20 +30,35 @@ def hash_trx(trx):
m.update(trx['purpose'].encode('utf-8'))
m.update(trx['date_fmt'].encode('utf-8'))
m.update(trx['entry_date_fmt'].encode('utf-8'))
m.update(str(trx['value']).encode('utf-8'))
m.update(str(trx['amount']).encode('utf-8'))
m.update(trx['currency'].encode('utf-8'))
m.update(trx['applicant_name'].encode('utf-8'))
return m.hexdigest()
def get_transactions(blz, iban, login, pin, endpoint, start, end):
logger.info('Connect to: %s', endpoint)
f = fints.client.FinTS3PinTanClient(blz, login, pin, endpoint)
def get_telegram_chat_id(config):
# Get chat id
conn = http.client.HTTPSConnection("api.telegram.org:443")
conn.request("POST", "/bot%s/getUpdates" % config['token'])
resp = conn.getresponse()
updates = json.loads(resp.read())
if updates['ok']:
for result in updates['result']:
chat = result['message']['chat']
if chat['username'] == config['username']:
return chat['id']
def get_transactions(config, start, end):
logger.info('Connect to: %s', config['server'])
f = fints.client.FinTS3PinTanClient(config['blz'], config['login'], config['pin'], config['server'])
accounts = f.get_sepa_accounts()
# Find correct account
account = [a for a in accounts if a.iban == iban][0]
account = [a for a in accounts if a.iban == config['iban']][0]
logger.info('Found account: %s', account)
statements = f.get_statement(account, start, end)
@ -53,16 +70,13 @@ def get_transactions(blz, iban, login, pin, endpoint, start, end):
return trxs, balance, account
def send_pushover(token, user, title, message, timestamp):
def send_pushover(config, message, timestamp):
conn = http.client.HTTPSConnection("api.pushover.net:443")
conn.request("POST", "/1/messages.json",
urllib.parse.urlencode({
"token": token,
"user": user,
"title": title,
**config,
"message": message,
"timestamp": int(timestamp),
"html" : 1
"timestamp": int(timestamp)
}),
{
"Content-type": "application/x-www-form-urlencoded"
@ -70,13 +84,42 @@ def send_pushover(token, user, title, message, timestamp):
)
conn.getresponse()
logger.debug('Send via pushover: %s', message)
logger.debug('Send via Pushover: %s', message)
def send_mqtt(config, data):
publish.single(
**config,
payload = json.dumps(data, skipkeys = True)
)
logger.debug('Send via MQTT: %s', data)
def send_telegram(config, state, message):
# Get chat id
if 'telegram' not in state:
state['telegram'] = {
'chat_id' : get_telegram_chat_id(config)
}
# Send message
conn = http.client.HTTPSConnection("api.telegram.org:443")
conn.request("POST", "/bot%s/sendMessage" % config['token'],
urllib.parse.urlencode({
"chat_id" : state['telegram']['chat_id'],
"text" : message,
"parse_mode" : "html"
}),
{
"Content-type": "application/x-www-form-urlencoded"
}
)
resp = conn.getresponse()
logger.debug('Sent via Telegram Bot: %s', message)
def send_mqtt():
pass
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level = logging.INFO)
fints_logger = logging.getLogger('fints')
fints_logger.setLevel(logging.WARN)
@ -86,8 +129,8 @@ if __name__ == "__main__":
try:
with open(os.path.expanduser("~/.pushfin.yaml")) as f:
config = yaml.load(f)
except:
logger.error("Failed to open configuration file at: ~/.pushfin.yaml")
except IOError:
logger.error("Failed to open configuration file: ~/.pushfin.yaml")
sys.exit(-1)
try:
@ -104,27 +147,21 @@ if __name__ == "__main__":
start = datetime.date.fromordinal(state['last']-1)
end = datetime.date.today()
trxs, balance, account = get_transactions(
config['fints']['blz'],
config['fints']['iban'],
config['fints']['login'],
config['fints']['pin'],
config['fints']['url'],
start, end,
)
trxs, balance, account = get_transactions(config['fints'], start, end)
logger.debug("Current state: %s", state)
for trx in trxs:
if 'entry_date' in trx:
trx['entry_date_ts'] = time.mktime(trx['entry_date'].timetuple())
trx['entry_date_fmt'] = trx['entry_date'].strftime(config['format_date'])
if 'date' in trx:
trx['date_ts'] = time.mktime(trx['date'].timetuple())
trx['date_fmt'] = trx['date'].strftime(config['format_date'])
tx = dict(trx)
if 'entry_date' in trx:
tx['entry_date_fmt'] = trx['entry_date'].strftime(config['format_date'])
tx['entry_date'] = time.mktime(trx['entry_date'].timetuple())
if 'date' in trx:
tx['date_fmt'] = trx['date'].strftime(config['format_date'])
tx['date'] = time.mktime(trx['date'].timetuple())
if 'amount' in trx:
trx['value'] = trx['amount'].amount
tx['amount'] = float(trx['amount'].amount)
for tpl in config['templates']:
field = list(tpl.keys())[0]
@ -133,36 +170,37 @@ if __name__ == "__main__":
value = trx[field]
if value in tpl[field]:
for entry in tpl[field][value]:
trx = {**entry, **trx}
tx = {**entry, **tx}
hash = hash_trx(trx)
data = {
'trx' : tx,
'bal' : {
'date_fmt' : balance.date.strftime(config['format_date']),
'date' : time.mktime(balance.date.timetuple()),
'amount' : float(balance.amount.amount),
'currency' : balance.amount.currency
}
}
hash = hash_trx(tx)
if hash in state['hashes']:
logger.info("Skipping old transaction: %s", hash)
continue
else:
logger.info("Processing transaction: %s", trx)
bal = {
'date' : balance.date,
'date_fmt' : balance.date.strftime(config['format_date']),
'value' : balance.amount.amount,
'currency' : balance.amount.currency
}
msg = config['format'].format(trx=trx, bal=bal)
logger.info("Processing transaction: %s", tx)
if 'mqtt' in config:
send_mqtt()
send_mqtt(config['mqtt'], data)
if 'telegram' in config:
fmt = config['telegram']['format'] if 'format' in config['telegram'] else config['format']
msg = fmt.format(**data)
send_telegram(config['telegram'], state, msg)
if 'pushover' in config:
send_pushover(
config['pushover']['token'],
config['pushover']['user'],
config['pushover']['title'],
msg,
trx['date_ts']
)
fmt = config['pushover']['format'] if 'format' in config['pushover'] else config['format']
msg = fmt.format(**data)
send_pushover(config['pushover'], msg, tx['date'])
# Remmeber that we already send this transaction
state['hashes'][hash] = trx['date'].toordinal()

View File

@ -1,6 +1,12 @@
format: "{trx.posting_text} {trx.dir} {trx.applicant_name}: <i>{trx.purpose}</i> <b><font color=\"{trx.color}\">{trx.value}</font></b> {trx.currency} (Balance: {bal.value} {bal.currency})"
format_date: "%Y-%m-%d"
fints:
server: "https://fints.ing-diba.de/fints/"
blz: "XXXXX"
iban: "XXXXX"
login: "XXXXX"
pin: "'XXXXX"
templates:
- status:
C: # Credit
@ -16,17 +22,21 @@ templates:
- currency_symbol: "$"
mqtt:
broker: localhost
topic: pushfin-ingdiba
topic: pushfin/ingdiba
hostname: 192.168.0.2
port: 1883
auth:
username: "guest"
password: "guest"
telegram:
format: "{trx[posting_text]} {trx[dir]} {trx[applicant_name]}: <i>{trx[purpose]}</i> <b>{trx[amount]}</b> {trx[currency]} (Balance: {bal[amount]} {bal[currency]})"
token: "XXXXX"
user: stv0g
pushover:
format: "{trx.posting_text} {trx.dir} {trx.applicant_name}: <i>{trx.purpose}</i> <b><font color=\"{trx.color}\">{trx.value}</font></b> {trx.currency} (Balance: {bal.value} {bal.currency})"
title: "Ing-DiBa"
user: XXXXX
token: XXXXX
fints:
url: "https://fints.ing-diba.de/fints/"
blz: "XXXXX"
iban: "XXXXX"
login: "XXXXX"
pin: "'XXXXX"
html: 1

View File

@ -24,12 +24,12 @@ def read(fname):
setup(
name = "pushfin",
version = "0.1.0",
version = "0.2.0",
author = "Steffen Vogel",
author_email = "post@steffenvogel.de",
description = "Publishes bank account statements via MQTT and Pushover.",
description = "Publishes bank account transactions and balances via MQTT, Telegram and Pushover.",
license = "GPL-3.0",
keywords = "HBCI FinTS Pushover MQTT",
keywords = "HBCI FinTS Pushover MQTT Telegram",
url = "https://github.com/stv0g/pushfin",
long_description = read('README.md'),
scripts=[
@ -40,7 +40,8 @@ setup(
],
install_requires = [
'pyyaml',
'fints'
'fints',
'paho-mqtt'
],
zip_safe = False,
classifiers = [