Compare commits

...

1 Commits

Author SHA1 Message Date
chris 6351a9e83a first bot draft 2020-11-01 23:29:03 +01:00
2 changed files with 151 additions and 0 deletions

View File

@ -32,3 +32,47 @@ Or use the prebuilt docker image:
docker run -e FIREFLY_API_HOST="https://your-firefly-installation" -e FIREFLY_PERSONAL_ACCESS_TOKEN="abcd....1234" -p 9449:5000 zknt/firefly-exporter
and scrape port 9449 on your docker host.
## XMPP Bot
There is a simple XMPP / Jabber bot which responds to budget queries.
### Installation
Install requirements:
pip install -r requirements-xmppbot.txt
Use your personal access token from above, setting the following variables
to your environment:
export FIREFLY_JABBER_ID="firefly@example.com" # The bots JID
export FIREFLY_JABBER_PASSWORD="abcd...1234" # The bots xmpp password
export FIREFLY_API_HOST="https://yout-firefly-installation"
export FIREFLY_PERSONAL_ACCESS_TOKEN="1234...abcd"
### Preparation
Register a XMPP / Jabber account for your bot.
*Caveat*: The bot does not support E2E and handles your financial data, so make
sure the account resides on the same server as your personal account which you will
use for querying.
Log into your bot account with a XMPP client and add your personal JID to your roster.
Querying will only work with a two-way subscription.
## Bot usage
Run `python xmppbot.py`.
From your personal account, send a normal chat message to your bot.
Available commands:
* `budgets` - list all budgets names
* `budget <budget>` - query data for given budget
### Docker
Run the prebuilt image:
docker run -e FIREFLY_JABBER_ID="firefly@example.com" -e FIREFLY_JABBER_PASSWORD="abcd...1234" -e FIREFLY_API_HOST="https://yout-firefly-installation" -e FIREFLY_PERSONAL_ACCESS_TOKEN="1234...abcd" zknt/firefly-xmppbot

107
xmppbot.py Normal file
View File

@ -0,0 +1,107 @@
import os
import signal
import asyncio
import aioxmpp
import firefly.budgets
class BudgetBot(object):
def message_received(self, msg):
if msg.type_ != aioxmpp.MessageType.CHAT:
return
if not msg.body:
return
if str(msg.from_).split('/')[0] not in self.controller:
print("Ignored message from: {}".format(msg.from_))
return
print("msg from: {}".format(msg.from_))
print("got: {}".format(list(msg.body.values())[0]))
budget_data = firefly.budgets._collect_budget_data()
command = list(msg.body.values())[0]
if command.lower() == "budgets":
reply = "Budgets:\n"
for budget in budget_data:
reply += "- *{}*\n".format(budget.get('name'))
elif command.lower().startswith("budget "):
cmd_budget = command.split(' ')[1].lower()
budget = [b for b in budget_data if b['name'].lower() == cmd_budget]
if len(budget) != 1:
reply = "Invalid budget name"
else:
reply = "Budget *{}*\n".format(budget[0]['name'])
reply += " Limit: {}\n".format(budget[0].get('limit'))
reply += " Spent: {:.2f}\n".format(budget[0].get('spent'))
reply += " Remaining: {:.2f}\n".format(budget[0].get('remaining'))
reply += " Remaining per day: {:.2f}".format(budget[0].get('remaining_per_day'))
else:
reply = "Please send command:\n"
reply += "- *budgets* list all budgets"
reply_msg = aioxmpp.Message(
type_=msg.type_,
to=msg.from_,
)
reply_msg.body[None] = reply
self.client.enqueue(reply_msg)
async def run_simple_example(self):
stop_event = self.make_sigint_event()
self.client.stream.register_message_callback(
aioxmpp.MessageType.CHAT,
None,
self.message_received,
)
print("listening... (press Ctrl-C or send SIGTERM to stop)")
await stop_event.wait()
async def run_example(self):
self.client = self.make_simple_client()
async with self.client.connected():
await self.run_simple_example()
def make_simple_client(self):
client = aioxmpp.PresenceManagedClient(
aioxmpp.JID.fromstr(os.environ.get('FIREFLY_JABBER_ID')),
aioxmpp.make_security_layer(os.environ.get('FIREFLY_JABBER_PASSWORD')),
)
self.roster = client.summon(aioxmpp.RosterClient)
self.roster.on_initial_roster_received.connect(
self._read_roster,
)
return client
def _read_roster(self):
self.controller = []
for item in self.roster.items.values():
self.controller.append(str(item.jid))
print("Roster entry: {}".format(item.jid))
print("Subscription: {}".format(item.subscription))
print("Approved: {}".format(item.approved))
print("Allowed controllers: {}".format(self.controller))
def make_sigint_event(self):
event = asyncio.Event()
loop = asyncio.get_event_loop()
loop.add_signal_handler(
signal.SIGINT,
event.set,
)
return event
def main():
bot = BudgetBot()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(bot.run_example())
finally:
loop.close()
if __name__ == "__main__":
main()