Build a Python Bot to Automate Your Expense Tracking with Discord and Google Sheets [Part1]
Phạm Tiến Thuận Phát

Phạm Tiến Thuận Phát @fuderrpham03

About: Proficient in AWS, Generative AI, Web Development, Mobile Development, LLMs, NLP (Natural Language Processing), WordPress, Tailwind CSS, Typescript, C#, Javascript, and Python

Location:
Ho Chi Minh City
Joined:
Jul 15, 2024

Build a Python Bot to Automate Your Expense Tracking with Discord and Google Sheets [Part1]

Publish Date: Jun 8
1 0

Source Code
LinkedIn
Tired of manually entering every single coffee purchase or subscription fee into a clunky spreadsheet? Me too. Manual expense tracking is tedious and prone to errors. What if you could just send a message to a personal bot and have everything logged, categorized, and visualized automatically?

In this step-by-step guide, we'll build exactly that. We'll create a personal Discord bot that listens for your expense messages, processes them, and pushes the data to a Google Sheet. To top it off, we'll build an interactive dashboard right within Google Sheets to see where your money is going.

The results image to easier visualization our goal:

🏷️ All WE NEED IS OPEN OUR PHONE AND CHAT WITH BOT
Image description

Here’s a sneak peek of the final result:

The Bot in Action:

The Interactive Dashboard:

Ready to build your own financial assistant? Let's dive in.

The Architecture: How It Works
Before we write any code, let's understand the flow of data. Our system has a simple but effective architecture:
Architecture_Diagram

Image description

You send a message, the bot picks it up, and the Google Sheet acts as both our database and our business intelligence tool.

Part 1: Setting Up the Foundations

This part involves configuring Discord and Google Cloud. It's a one-time setup that gives our bot the permissions it needs.

Step 1: The Discord Bot
First, we need to create a bot user in Discord.

1. Create an Application: Go to the Discord Developer Portal, click "New Application," and give it a name.

2. Create a Bot: Navigate to the "Bot" tab and click "Add Bot."

3. Enable Intents: This is crucial. You must enable the MESSAGE CONTENT INTENT. This allows your bot to read the content of messages.

4. Get Your Token: On the same "Bot" tab, click "Reset Token" to get your secret bot token. Copy it and save it somewhere safe.

5. Invite the Bot: Go to the "OAuth2" -> "URL Generator" tab. Select the bot scope, and then grant it Send Messages and Read Message History permissions. Copy the generated URL and paste it into your browser to invite the bot to your server.

Step 2: The Google Sheets "Database"
Next, we'll set up the Google Sheets API so our Python script can write data to it.

1. Create a Google Cloud Project: Go to the Google Cloud Console, create a new project, and give it a name.

2. Enable APIs: In your project, search for and enable two APIs: Google Drive API and Google Sheets API.

3. Create a Service Account: In "APIs & Services" -> "Credentials," create a new Service account. Give it a name and grant it the Editor role.

4. Generate a JSON Key: After creating the service account, go to its "Keys" tab, add a new key, select JSON, and create it. A credentials.json file will be downloaded. Place this file in your project folder.

5. Share Your Sheet: Create a new Google Sheet. Open the credentials.json file, copy the client_email value, and share your Google Sheet with this email address, giving it Editor access.

Part 2: The Brain - The Python Code

Now for the fun part. Here is the complete Python script for our bot. Save it as main.py.

import asyncio
import sys
import discord
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import re
from datetime import datetime
import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv() 

# --- Bot Setup ---
# This code block fixes an EventLoop issue on Windows
if sys.platform == 'win32':
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

# --- Configuration ---
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
GOOGLE_SHEET_NAME = os.getenv('GOOGLE_SHEET_NAME')
ALLOWED_CHANNEL = os.getenv('ALLOWED_CHANNEL')

# Expense categories dictionary
CATEGORIES = {
    'Food & Drink': ['ăn', 'uống', 'cà phê', 'cf', 'nhà hàng', 'quán', 'trà sữa', 'bữa trưa', 'bữa tối'],
    'Transportation': ['xăng', 'grab', 'be', 'bus', 'taxi', 'vé xe', 'gửi xe'],
    'Utilities': ['điện', 'nước', 'internet', 'net', 'thuê nhà', '4G'],
    'Shopping': ['quần áo', 'giày', 'mỹ phẩm', 'sách', 'mua sắm', 'shopee', 'lazada'],
    'Entertainment': ['xem phim', 'game', 'du lịch', 'concert'],
    'Self-development': ['thi certification', 'khóa học', 'English', 'cert', 'IELTS', 'gym'],
    'IT-Industry': ['GPT', 'Cursor', 'AWS', 'AI'],
    'Networking': ['Mời nước', 'mời cơm trưa', 'network']
}

# --- Core Logic Functions ---
def get_gsheet_client():
    """Authorizes the gspread client and returns it."""
    scope = ["https://spreadsheets.google.com/feeds", 'https://www.googleapis.com/auth/spreadsheets',
             "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive"]
    creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
    client = gspread.authorize(creds)
    return client

def parse_message(text):
    """
    Parses a message to extract description and amount.
    Handles 'k' suffix for thousands (e.g., 10k -> 10000).
    """
    match = re.search(r'(.*):\s*([\d.,]+)\s*([kK]?)', text.strip())
    if match:
        description = match.group(1).strip()
        amount_str = match.group(2).replace('.', '').replace(',', '')
        suffix = match.group(3).lower()
        try:
            amount = float(amount_str)
            if suffix == 'k':
                amount *= 1000
            return description, int(amount)
        except ValueError:
            return None, None
    return None, None

def categorize_expense(description):
    """Automatically categorizes an expense based on keywords."""
    description_lower = description.lower()
    for category, keywords in CATEGORIES.items():
        for keyword in keywords:
            if keyword.lower() in description_lower:
                return category
    return 'Other'

# --- Discord Bot Events ---
gsheet_client = get_gsheet_client()
worksheet = gsheet_client.open(GOOGLE_SHEET_NAME).sheet1

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():
    """Called when the bot successfully connects."""
    print(f'Bot has logged in as {client.user}')
    print('Bot is ready!')

@client.event
async def on_message(message):
    """Called for every new message."""
    if message.author == client.user: return
    if message.channel.name != ALLOWED_CHANNEL: return

    description, amount = parse_message(message.content)

    if description and amount:
        category = categorize_expense(description)
        now = datetime.now()
        date_today = now.strftime('%d/%m/%Y')
        time_now = now.strftime('%H:%M:%S')
        new_row = [date_today, time_now, category, description, amount]

        try:
            worksheet.append_row(new_row)
            await message.channel.send(
                f'✅ **Successfully Logged!**\n\n'
                f'📅 **Date:** {date_today}\n'
                f'⏰ **Time:** {time_now}\n'
                f'🏷️ **Category:** {category}\n'
                f'📝 **Item:** {description}\n'
                f'💰 **Amount:** {amount:,} VND'
            )
        except Exception as e:
            print(f"Error writing to Google Sheet: {e}")
            await message.channel.send('Sorry, there was an error saving to Google Sheets.')

# --- Main function to run the bot ---
if __name__ == '__main__':
    # Setup environment variables in a .env file
    # DISCORD_TOKEN=...
    # GOOGLE_SHEET_NAME=...
    # ALLOWED_CHANNEL=...
    client.run(DISCORD_TOKEN)
Enter fullscreen mode Exit fullscreen mode

Part 3: The Interactive Dashboard in Google Sheets

This is where we turn our raw data into valuable insights without writing any more code.

1. Create a "Month" Helper Column: In your data sheet (WalletManagement), go to the next empty column (e.g., F1), title it "Month," and paste this formula into cell F2. It will automatically populate the month for every entry.

=ARRAYFORMULA(IF(A2:A<>"", IFERROR(TEXT(A2:A, "MM/YYYY")), ""))

Note: Depending on your region, you may need to replace the comma , with a semicolon ;.
Enter fullscreen mode Exit fullscreen mode

2. Create a Pivot Table: In a new sheet (call it Dashboard), go to Insert -> Pivot table. Use your WalletManagement sheet as the data source.

Set Rows to Hạng mục (Category).
Set Values to Số tiền (Amount), summarized by SUM.

3. Add a Slicer: This is our "remote control." Go to Data -> Add a slicer. In the slicer settings, choose the "Month" column as your filter. Now you can click the slicer and select any month to filter your report!

4. Add the Pie Chart: Select the data in your Pivot Table (the categories and their totals). Go to Insert -> Chart and choose a Pie Chart. It will automatically link to the pivot table and update whenever you use the slicer.

Conclusion and Next Steps

And there you have it! A fully automated expense tracker tailored to your needs. You've connected a Discord bot to Google's powerful spreadsheet and visualization tools, creating a system that's both practical and fun.

From here, the possibilities are endless:

  • Deploy the Bot: Deploy the Python script to a free cloud service like Render or a Raspberry Pi so it runs 24/7.

  • Add More Commands: Create commands like !summary or !last5 to get insights directly within Discord.

  • Deeper Analysis: Use more advanced Google Sheets functions or connect your sheet to Google Looker Studio for even more powerful BI dashboards.
    Happy coding, and may your budgets be ever in your favor!

Comments 0 total

    Add comment