first bot draft
This commit is contained in:
parent
340ff5270e
commit
f2a5ec3990
3 changed files with 152 additions and 0 deletions
44
README.md
44
README.md
|
@ -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
|
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.
|
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
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
aioxmpp==0.11.0
|
aioxmpp==0.11.0
|
||||||
requests==2.24.0
|
requests==2.24.0
|
||||||
|
python-dateutil==2.8.1
|
||||||
|
|
107
xmppbot.py
Normal file
107
xmppbot.py
Normal 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()
|
Loading…
Reference in a new issue