Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Python application test

on:
push:
branches: [ '*' ]

jobs:
build:

runs-on: ubuntu-latest
environment: Test
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9.13
uses: actions/setup-python@v2
with:
python-version: 3.9.13
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
env:
FIREBASE_JSON: ${{ secrets.FIREBASE_JSON }}
run: |
python -m pytest
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,7 @@ cython_debug/
.vercel

# My stuff
test_func.py
test_func.py
users_backup.json
one_off_func.py
*.json
27 changes: 27 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## FAQ on TeleFinance Tracker Bot

### 1. Sometimes the bot doesn't reply when I enter data. What should I do?
The bot may occasionally encounter issues due to hosting limitations. If it doesn't respond, try waiting, or use `/cancel` and re-enter the data. Most of the time, `/cancel` would solve majority of your issues!

### 2. My entry ends up in the wrong category. How do I fix this?
When keying new entry, do ensure that you have received the "Transaction logged." message before adding a new one. For your current incorrect entries, you can manually adjust them in the Google Sheet.

### 3. How do I add past transactions or entries for previous months?
For entries from past months, you can manually add them directly to the Google Sheet. For transactions earlier in the current month, use the `/backlog` command.

### 4. How do I delete or edit a past entry?
To delete or edit past entries, manually adjust them in the Google Sheet. Remember not to shift the remaining entries up, as this could disrupt new entries.

### 5. Can I add or edit quick settings for `/quickothers` or `/quicktransport`?
Yes, you can customize these settings by modifying the tracker tab in the Google Sheet. There is a limit set in the bot to prevent excessively long lists.

### 6. How do I view all the commands available?
You can view all available commands by opening the menu in the Telegram chat or by typing `/help`.

### 7. I edited the Google Sheet, but the bot doesn't seem to recognize the changes. What should I do?
Do ensure that `TRACKER` under the Tracker tab is correctly updated. The first row refers to the row number the first entry is on, the Transport Row and Other Row should refers to the last entry of the respective category. This means there's possiblity that the Other or Transport Row is -1 of first row.

![tracker fixed](https://github.com/brucewzj99/tele-tracker-v2/doc-image/faq-tracker.png)


### If you have additional questions or need further assistance, feel free to [open a new issue](https://github.com/brucewzj99/tele-tracker-v2/issues) on GitHub.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Bruce Wang
Copyright (c) 2024 Bruce Wang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ A python telegram bot to help track daily expenses onto google sheet, hosted on
## Release Notes
You can find the release notes over [here](https://github.com/brucewzj99/tele-tracker-v2/blob/master/release_notes.md).

## FAQ
You can find the FAQ over [here](https://github.com/brucewzj99/tele-tracker-v2/blob/main/FAQ.md).

## Table of Contents
- [Getting Started (Users)](#getting-started-users)
- [Getting Started (Developers)](#getting-started-developers)
Expand Down Expand Up @@ -35,15 +38,14 @@ pip install -r requirements.txt
* Set up Google Sheet API, download service account key
* Retrieve Google Sheet API email
* Set up Firebase Firestore Database, download service account key
* Retrieve your firebase database url
* Set up telegram bot via [BotFather](https://t.me/BotFather)
* Insert all of them into .env as follows, you can use py dotenv or set it as env variable in your venv
* You can use the same TOKEN for BOT_TOKEN & TEST_TOKEN but I recommend using two different bots for testing and production
* I recommend having two different firebase databases for testing and production

``` .env
BOT_TOKEN=your_bot_token
TEST_TOKEN=your_test_token
DATABASE_URL=firebase_url
GOOGLE_API_EMAIL=google_api_email
FIREBASE_JSON=service_account_key
GOOGLE_JSON=service_account_key
Expand All @@ -53,9 +55,9 @@ MASTER_TELE_ID=your_telegram_id
### Step 3
* Proceed to project directory and run:
``` python
python3.9 test_polling.py
python3.9 dev_polling.py
OR
python3.9 test_webhook.py
python3.9 dev_webhook.py
```

## Usage
Expand Down
78 changes: 62 additions & 16 deletions bot/database_service/firestore_service.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,93 @@
from bot.database_service.auth import get_db_client
from datetime import datetime
from datetime import datetime, timedelta
import pytz


class FirestoreService:
"""
This class is responsible for managing the Firestore database.
"""

def __init__(self):
def __init__(self, collection_name="users"):
self.db = get_db_client()
self.collection_name = collection_name

# New user setup
def new_user_setup(self, telegram_id, sheet_id, telegram_username):
user_ref = self.db.collection("users").document(str(telegram_id))
user_ref.set({
"sheet_id": sheet_id,
"datetime_created": datetime.now(),
"username": telegram_username
})
user_ref = self.db.collection(self.collection_name).document(str(telegram_id))
timestamp = datetime.now(pytz.timezone("Asia/Singapore"))
user_ref.set(
{
"sheet_id": sheet_id,
"datetime_created": timestamp,
"username": telegram_username,
"usage_count": 0,
"last_accessed": timestamp,
"hourly_accessed": timestamp,
"overusage_count": 0,
}
)

# Check if user exists
def check_if_user_exists(self, telegram_id):
user_ref = self.db.collection("users").document(str(telegram_id))
user_ref = self.db.collection(self.collection_name).document(str(telegram_id))
user_doc = user_ref.get()
return user_doc.exists

# Get user sheet id
def get_user_sheet_id(self, telegram_id):
user_ref = self.db.collection("users").document(str(telegram_id))
def get_user_sheet_id(self, telegram_id, telegram_username):
user_ref = self.db.collection(self.collection_name).document(str(telegram_id))
user_doc = user_ref.get()

if user_doc.exists:
return user_doc.get("sheet_id")
else:
return None
try:
# Update username if it is different
if user_doc.get("username") != telegram_username:
user_ref.update({"username": telegram_username})

# Get the current time
now = datetime.now(pytz.timezone("Asia/Singapore"))
# Retrieve the hourly accessed time
hourly_accessed = user_doc.get("hourly_accessed")

if not hourly_accessed:
hourly_accessed = now

usage_count = user_doc.get("usage_count")
overusage_count = user_doc.get("overusage_count")
if (now - hourly_accessed) < timedelta(hours=1):
if usage_count < 30:
usage_count += 1
else:
overusage_count += 1 # Increment overusage count if limit reached within the hour
else:
# Reset if a new hour has started
usage_count = 1
hourly_accessed = now

# Update the last accessed time, usage count and overusage count
user_ref.update(
{
"last_accessed": now,
"hourly_accessed": hourly_accessed,
"usage_count": usage_count,
"overusage_count": overusage_count,
}
)
return user_doc.get("sheet_id")
except Exception as e:
raise e
return None

# Get all user IDs
def get_all_user_id(self):
users_ref = self.db.collection("users")
users_ref = self.db.collection(self.collection_name)
user_ids = [int(user.id) for user in users_ref.stream()]
return user_ids

# Get all sheet IDs
def get_all_sheet_id(self):
users_ref = self.db.collection("users")
users_ref = self.db.collection(self.collection_name)
sheet_ids = []
for user in users_ref.stream():
sheet_ids.append(user.get("sheet_id"))
Expand Down
12 changes: 0 additions & 12 deletions bot/firestore_config.py

This file was deleted.

45 changes: 0 additions & 45 deletions bot/firestore_service.py

This file was deleted.

Loading