If you are reading this I assume you know about splitwise application. A one-line summary in their words is "Splitwise is a free tool for friends and roommates to track bills and other shared expenses so that everyone gets paid back"
I use another app called money manager for personal expenses, but instead of jumping between applications I always wanted one app to track all my expenses. I was about to send feedback to Splitwise to add a feature where I can add costs only where I am involved, interestingly enough many users needed this feature and thanks to them the Splitwise team have suggested a way to do it.
All my expenses are in the same app, now what? Although all my expenses are in the same place, Splitwise doesn't have a feature to tell me how much I've spent in the previous month across the application. So the curious person in me wanted to solve this problem, that is how this Side-Project-Sunday is born.
Now as the storytime is done, let's list down all the requirements
- Ability to get the total expense
- For a given date/date range
- For a given friend(s)/group(s)
- Output a pie chart for a given date range
- For Group-wise expenses
- For Category-wise expenses
- Send an email on the first day of the month, with the following template
With a quick googling about how we can do it, I found that Splitwise is supported in automation apps like Zapier, make etc.., although with a little bit of effort we can use these apps to fulfil my requirements, I thought it would be better if I code it myself so that I'll have more control on the output.
Further googling landed me on Splitwise dev documentation and I am so happy that there are a few developers who have developed third-party SDKs using the exposed APIs.
For sending out the email, I wanted to use Gmail's SMTP server to send out the email. A quick search through the web helped me understand how I can do it. I've linked all the references that I've taken help from at the end of the article.
As I was planning to push my script to GitHub, I was thinking to have a GitHub action to run my script on a regular interval. GitHub supports multiple ways of triggering actions, of which one is cron expression. This should help me in running the script at the end of every month.
At first, I started exploring the SDK to understand all the APIs I could use to fulfil my requirements.
First I've created a new application in Splitwise, this generates
Consumer Key and
Consumer Secret for us which we'll be using to authenticate our calls.
As I plan to use this for a personal use case I've generated an API key, instead of going through the hassle of setting up OAuth1/OAuth2.
Let's go through all the APIs that Splitwise supports and list down the methods in which we are interested.
This API is so powerful that most of our logic will rely on this, the
getExpenses method supports a wide variety of filters with which we can filter the data based on
friendIds etc.., As I'll need the data for the past month, I'll just get all the expenses for the previous month date range. Unfortunately, this API doesn't give us all the details in one network call hence we need to get all the data by making multiple network calls.
s = Splitwise(CONSUMER_KEY, CONSUMER_SECRET, api_key=API_KEY) expenses =  current_page_num = 0 page_size = 100 while True: curr_page_expenses = s.getExpenses( dated_after=start_date, dated_before=end_date, limit=page_size, offset=current_page_num * page_size, ) if len(curr_page_expenses) == 0: break current_page_num = current_page_num + 1 expenses = expenses + curr_page_expenses
getFriends method fetches us all the friends and group data to us which we can map with the expense data that we've fetched earlier.
groups = s.getGroups() friends = s.getFriends()
We can traverse through friends' data and filter out friends where we owe them or where we are owed.
def get_friends_with_outstanding_table(friends): friends_with_outstanding_balances =  for f in friends: balance = sum(map(lambda b: float(b.amount), f.balances)) if balance != 0: color = "red" if balance < 0 else "green" friends_with_outstanding_balances.append([f.first_name, balance, color]) friends_with_outstanding_balances.sort(key=lambda f: f, reverse=True) return friends_with_outstanding_balances
When it comes to visualization in python, most of us are familiar with matplotlib. The same is the case with me, so I used matplotlib to generate the charts.
As I mentioned earlier, I wanted to use Gmail's SMTP server to send out emails. There are plenty of articles available on how to setup and send out emails using Gmail which I'll link at the end of the article.
Let's first create a Github action to run our script and send out the email. Let the name of the job be send-email
jobs: send-mail: runs-on: ubuntu-latest steps:
The first step of our job is to checkout the repository with the default branch. There is already an action available in GitHub Action Marketplace for this.
jobs: send-mail: runs-on: ubuntu-latest steps: - name: Checkout Repos uses: email@example.com
Next, set up the python environment so that our script can run without any issues. I'll be using setup-python from marketplace to install python and pip.
jobs: send-mail: runs-on: ubuntu-latest steps: - name: Checkout Repos uses: firstname.lastname@example.org - name: Setup Python uses: email@example.com with: python-version: 3.8.0 cache: pip architecture: x64 update-environment: true
Finally, let's install all the required libraries using pip and then run our script.
jobs: send-mail: runs-on: ubuntu-latest steps: - name: Checkout Repos uses: firstname.lastname@example.org - name: Setup Python uses: email@example.com with: python-version: 3.8.0 cache: pip architecture: x64 update-environment: true - name: Install required dependencies run: python -m pip install -r requirements.txt - name: Run Automation run: python main.py
As our action is ready to go, let's now try to schedule the action at regular intervals. GitHub supports cron expressions as
schedule which we'll be using to schedule our script to run on the 1st of every month.
on: push: branches: ["main"] schedule: - cron: 0 10 1 * *
The complete yml file can be found here.