added support for Telegram and MQTT
This commit is contained in:
parent
d0b88bfda3
commit
7fbe368926
4 changed files with 124 additions and 72 deletions
15
README.md
15
README.md
|
@ -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 |
|
||||
|
|
142
bin/pushfin
142
bin/pushfin
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
9
setup.py
9
setup.py
|
@ -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 = [
|
||||
|
|
Loading…
Add table
Reference in a new issue