main.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # -*- coding: utf-8 -*-
  2. import argparse
  3. import logging
  4. import sys
  5. import datetime
  6. import json
  7. import hammock
  8. import requests
  9. from ynrc import __version__
  10. __author__ = "Nikola Kotur"
  11. __copyright__ = "Nikola Kotur"
  12. __license__ = "mit"
  13. _logger = logging.getLogger(__name__)
  14. def parse_args(args):
  15. parser = argparse.ArgumentParser(description="You Need Rocket.Chat: send weekly YNAB reports to your Rocket.Chat instance")
  16. parser.add_argument(
  17. "--version",
  18. action="version",
  19. version="ynrc {ver}".format(ver=__version__),
  20. )
  21. parser.add_argument("--ynab-api-key", help="YNAB API key", type=str, required=True)
  22. parser.add_argument("--ynab-budget-name", help="YNAB budget name", type=str, required=True)
  23. parser.add_argument("--rocketchat-webhook", help="Rocket.Chat webhook URL", type=str, required=True)
  24. parser.add_argument("--rocketchat-avatar-url", help="Rocket.Chat user avatar URL", type=str, default="")
  25. parser.add_argument(
  26. "-v",
  27. "--verbose",
  28. dest="loglevel",
  29. help="set loglevel to INFO",
  30. action="store_const",
  31. const=logging.INFO,
  32. )
  33. parser.add_argument(
  34. "-vv",
  35. "--very-verbose",
  36. dest="loglevel",
  37. help="set loglevel to DEBUG",
  38. action="store_const",
  39. const=logging.DEBUG,
  40. )
  41. return parser.parse_args(args)
  42. def setup_logging(loglevel):
  43. logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
  44. logging.basicConfig(
  45. level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S"
  46. )
  47. def _get_group_name_by_id(groups, group_id):
  48. for group in groups:
  49. if group_id == group['id']:
  50. return group['name']
  51. return ''
  52. def fetch_from_ynab(api_key, budget_name):
  53. _logger.debug("Fetching YNAB budget %s...", budget_name)
  54. ynab = hammock.Hammock(
  55. "https://api.youneedabudget.com/v1",
  56. headers={"Authorization": "Bearer %s" % api_key},
  57. )
  58. budgets = ynab.budgets.GET().json()['data']['budgets']
  59. for budget in budgets:
  60. if budget['name'] == budget_name:
  61. budget_id = budget['id']
  62. budget_full = ynab.budgets(budget_id).GET().json()['data']['budget']
  63. category_groups = budget_full['category_groups']
  64. break
  65. else:
  66. raise Exception('Budget with name "%s" not found' % budget_name)
  67. _logger.debug('Using %s (%s)', budget_name, budget_id)
  68. today = datetime.date.today()
  69. week_ago = today - datetime.timedelta(days=7)
  70. since_date = week_ago.strftime('%Y-%m-%d')
  71. _logger.debug('Fetching all transactions since %s...', since_date)
  72. transactions = ynab.budgets(budget_id).transactions.GET('?since_date=%s' % since_date).json()['data']['transactions']
  73. # Calculate spending
  74. res = {}
  75. for tx in transactions:
  76. if tx['subtransactions']:
  77. tx = tx['subtransactions']
  78. else:
  79. tx = [tx]
  80. for t in tx:
  81. if t['amount'] > 0:
  82. continue
  83. tid = t['category_id']
  84. if tid not in res:
  85. res[tid] = {
  86. 'amount': 0.0,
  87. }
  88. res[tid]['name'] = t.get('category_name', None)
  89. res[tid]['amount'] += t['amount']
  90. # Get category names
  91. for tid, t in res.items():
  92. if tid is None:
  93. res[tid]['name'] = 'Transfer'
  94. continue
  95. cat = ynab.budgets(budget_id).categories(tid).GET().json()['data']['category']
  96. parent_cat = _get_group_name_by_id(category_groups, cat['category_group_id'])
  97. res[tid]['name'] = parent_cat + ': ' + cat['name']
  98. # Sort top 10 results
  99. out = []
  100. for r in (sorted(res.values(), key=lambda k: k['amount'])):
  101. amount = u'%4.2f €' % abs(r['amount'] / 1000)
  102. out.append((u'%s' % r['name'], amount, ))
  103. if len(out) >= 10:
  104. break
  105. return out
  106. def post_to_rocketchat(hook, avatar, ynab):
  107. if not ynab:
  108. return
  109. today = datetime.date.today()
  110. week_ago = today - datetime.timedelta(days=7)
  111. post_data = {
  112. "alias": "YNAB",
  113. "text": 'Report for week of %s (from %s)' % (today.strftime('%Y-%W'), week_ago.strftime('%a %Y-%m-%d')),
  114. "attachments": [
  115. ],
  116. }
  117. if avatar:
  118. post_data['avatar'] = avatar
  119. fields = []
  120. for spending in ynab:
  121. spending_name, spending_amount = spending
  122. fields.append({
  123. "title": spending_name,
  124. "value": spending_amount,
  125. })
  126. post_data['attachments'] = [{
  127. 'title': 'Top 10 categories spent',
  128. 'fields': fields,
  129. }]
  130. result = requests.post(
  131. hook,
  132. headers={'Content-Type': 'application/json'},
  133. data=json.dumps(post_data),
  134. )
  135. if result.json().get('success', False):
  136. _logger.debug("Posted to Rocket.Chat successfully")
  137. else:
  138. _logger.error("Failed posting to Rocket.Chat: %s". result.text)
  139. def main_call(args):
  140. args = parse_args(args)
  141. setup_logging(args.loglevel)
  142. _logger.debug("Starting ynrc...")
  143. ynab = fetch_from_ynab(args.ynab_api_key, args.ynab_budget_name)
  144. post_to_rocketchat(args.rocketchat_webhook, args.rocketchat_avatar_url, ynab)
  145. _logger.debug("Finished running ynrc")
  146. def run():
  147. """Entry point for console_scripts"""
  148. main_call(sys.argv[1:])
  149. if __name__ == "__main__":
  150. run()