diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
new file mode 100644
index 000000000..9cdfd5f70
--- /dev/null
+++ b/.github/workflows/workflow.yml
@@ -0,0 +1,4 @@
+- name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 000000000..25b374560
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,37 @@
+[MASTER]
+
+# Specify the maximum line length (79 is PEP 8's recommendation, but you can change it)
+max-line-length = 79
+
+[MESSAGES CONTROL]
+
+# Disable some specific warnings or errors
+disable = C0114, C0115, C0116
+
+[BASIC]
+
+# Naming style (snake_case)
+method-rgx=_*[a-z_][a-z0-9_]*$
+function-rgx=_*[a-z_][a-z0-9_]*$
+attr-rgx=_*[a-z_][a-z0-9_]*$
+
+[FORMAT]
+
+# Ensure consistent whitespace in function call
+remove-trailing-whitespace = yes
+
+# Add or remove whitespace where necessary
+single-space-after-comma = yes
+single-space-after-keywords = yes
+
+[TYPECHECK]
+
+# Enable type hints and type checking (Python 3.5+)
+init-import = yes
+ignore-mixin-members = yes
+
+[IMPORTS]
+
+# Allow wildcard imports in specific cases (e.g., __init__.py)
+allow-wildcard-with-all = yes
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c9af5982f..382f54649 100755
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,10 @@
-# Contributing to MyDollarBot
+# Contributing to DollarSplitBot
-Follow the set of guidelines below to contribute to MyDollarBot!
+Follow the set of guidelines below to contribute to DollarSplitBot!
## Code of Conduct
-This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/usmanwardag/dollar_bot/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to upload this code. Please report unacceptable behavior to sbose2@ncsu.edu.
+This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/shonilbhide/dollar_bot/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to upload this code. Please report unacceptable behavior to csc510group32@gmail.com.
Prerequistes required before starting this project:
@@ -41,7 +41,7 @@ A cursory search is necessary to check if the reported bug is already mentioned
## To Submit A Good Bug Report
-[GitHub issues](https://github.com/usmanwardag/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug.
+[GitHub issues](https://github.com/shonilbhide/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug.
* To identify the problem, give the issue a clear and informative term.
@@ -70,7 +70,7 @@ Check out this [debugging guide](https://flight-manual.atom.io/hacking-atom/sect
## To Submit A Good Enhancement Suggestion
-[GitHub issues](https://github.com/usmanwardag/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug.
+[GitHub issues](https://github.com/shonilbhide/dollar_bot/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [the template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) to give details of the bug.
* To identify the problem, give the issue a clear and informative term.
* Describe in as much detail as possible to duplicate the problem. Explain the problem and explain about the exact command sused in the terminal which caus ethe bug to occur.
diff --git a/README.md b/README.md
index 8e27f5f57..70c825d6e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-# 💰 MyDollar Bot 💰
+# 💰 DollarSplitBot 💰
Table of Contents
- - Why should you use Dollar Bot?
+ - Why should you use DollarSplitBot?
- Check out the video!
- What is new in this version?
- Installation
@@ -32,7 +32,7 @@
[](https://desktop.telegram.org/)

[](https://github.com/sak007/MyDollarBot-BOTGo/graphs/contributors)
-[](https://doi.org/10.5281/zenodo.5759217)
+[](https://zenodo.org/records/10015948)
[](https://app.travis-ci.com/usmanwardag/dollar_bot)
[](https://codecov.io/gh/usmanwardag/dollar_bot)
@@ -42,16 +42,25 @@
-## Why should you use MyDollar Bot?
+## Why should you use DollarSplitBot?
-Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle.
-With simple commands, this bot allows you to:
-- Add/Record new spendings
-- Display your spendings through bar graph
-- Show the sum of your expenditure for the current day/month
-- Display your spending history
-- Clear/Erase all your records
-- Edit/Change any spending details if you wish to
+"Discover a whole new level of financial clarity and fairness – where you'll never have to wonder 'Who owes me, and who do I owe?' again. Say goodbye to financial puzzles, and embrace our extended expense management system to reclaim your peace of mind!"
+
+Introducing DollarSplitBot, your trusty companion on Telegram, here to turn the mundane task of tracking your daily expenses into a breeze. This ingenious bot simplifies the process of keeping tabs on your spending, even when you're offline. But that's not all; it's also your go-to solution for managing group expenses and ensuring everyone's financial equilibrium.
+
+With just a few swift commands, DollarSplitBot empowers you to:
+
+1. **Welcome New Faces:** Add your friends to share expenses with ease.
+2. **Log Your Transactions:** Document and store your expenditures effortlessly.
+3. **Equitable Divisions:** Showcase your spending history, unraveling who owes what to whom.
+4. **Money Matters:** Keep tabs on your daily and monthly expenditure totals.
+5. **Your Financial Story:** Access your spending history at any time.
+6. **A Clean Slate:** Erase all records when it's time to start anew.
+7. **Tailored Details:** Edit any spending particulars to your liking.
+8. **Paper Trail:** Generate sleek PDF expenditure reports for a comprehensive overview.
+9. **Friendly Nudges:** Send friendly reminders via email to ensure financial settlements.
+
+DollarSplitBot: Where simplicity meets financial harmony at your fingertips.
## Check out the video!
@@ -105,6 +114,26 @@ A successful run will generate a message on your terminal that says "TeleBot: St
To run the script automatically at startup / reboot, simply add the `.run_forever.sh` script to your `.bashrc` file, which executes whenever you reboot your system.
+## Configuring Email Credentials for SMTP: Sending Emails from Your Account
+
+**SMTP (Simple Mail Transfer Protocol)** is a standard protocol for sending emails. It is widely used for sending email messages from one server to another. In the code, we are using SMTP to send emails via a Gmail account. Here's how the SMTP configuration and usage work:
+
+1. **SMTP Server**: The `smtp_server` variable is set to 'smtp.gmail.com,' which is the SMTP server for Gmail. This server is responsible for sending your email messages.
+
+2. **SMTP Port**: The `smtp_port` variable is set to 587. This is the port for TLS (Transport Layer Security) encryption. Gmail uses this port for secure email communication.
+
+3. **SMTP Username**: The smtp_username variable should be set to your own Gmail email address from which you want to send the emails. Make sure to replace `your-email@gmail.com` with your actual Gmail email address in the code. This ensures that the emails will be sent from your specific Gmail account.
+
+4. **SMTP Password**: The `smtp_password` variable is set, you need to generate an "App Password". An App Password is a 16-character code that allows you to access your Gmail account without revealing your real password. To generate an App Password, follow these steps:
+
+ - Go to your Google Account settings (https://myaccount.google.com/).
+ - In the "Security" section, under "Signing in to Google", select "App Passwords".
+ - Select "Mail" and "Other (Custom name)" from the dropdown menus.
+ - Click "Generate".
+ - Google will provide you with a 16-character App Password. Use this as your `smtp_password` in your code.
+
+By customizing these settings, you can send emails from any email account using SMTP. Just ensure you are adhering to the security guidelines provided by your email provider.
+
## Testing
We use pytest to perform testing on all unit tests together. The command needs to be run from the home directory of the project. The command is:
@@ -123,34 +152,56 @@ coverage run -m pytest test/
coverage report
```
+## Use Cases
+
+Common use cases for DollarSplitBot summarized in three points:
+
+1. **Personal Expense Tracking:**
+ - Easily log and monitor your individual daily and monthly expenses, including groceries, dining out, transportation, and entertainment.
+ - Access your spending history and totals at any time, providing valuable insights into your financial habits.
+
+2. **Group Expense Management:**
+ - Efficiently manage group expenses with friends or family members. Add participants to track shared costs and responsibilities.
+ - DollarSplitBot calculates equitable divisions, simplifying the process of determining who owes what to whom in group expenses.
+
+3. **Expense Reporting and Communication:**
+ - Generate detailed PDF expenditure reports for a comprehensive overview of your financial activity.
+ - Utilize the bot's email reminders to facilitate financial settlements and maintain harmony in shared expenses, ensuring everyone is accountable.
+
+Certainly, you can watch this video [](https://youtu.be/aCjcT1CHAzU) for a step-by-step guide on how to use DollarSplitBot.
+
## License
-This project is licensed under the terms of the MIT license. Please check [License](https://github.com/usmanwardag/dollar_bot/blob/main/LICENSE) for more details.
+This project is licensed under the terms of the MIT license. Please check [License](https://github.com/shonilbhide/dollar_bot/blob/main/LICENSE) for more details.
## Code Documentation
-Checkout the [docs](https://github.com/sak007/MyDollarBot-BOTGo/tree/main/docs)
+Checkout the [docs](https://github.com/shonilbhide/dollar_bot/tree/main/docs)
## How to Contribute
-We would be happy to receive contributions! If you'd like to, please go through our [CONTRIBUTING.md](https://github.com/usmanwardag/dollar_bot/blob/main/CONTRIBUTING.md)
+We would be happy to receive contributions! If you'd like to, please go through our [CONTRIBUTING.md](https://github.com/shonilbhide/dollar_bot/blob/main/CONTRIBUTING.md)
-For any feedback, issues, or bug reports, please create an issue [here](https://github.com/usmanwardag/dollar_bot/issues/new).
+For any feedback, issues, or bug reports, please create an issue [here](https://github.com/shonilbhide/dollar_bot/issues/new).
## Future RoadMap
- More content can be added for the way notifications can be displayed on the user front. This can be done to make the UI more interactive.
- Recurring expenses feature can be added for faster addition of expenses instead of following the whole process of everytime.
+- This application can be integrated with a group chat to track expenses of a group.
+- A better model can be implemented to forecast the budgets and expenses for future.
+- Make our bot support multiple languages, and not just english so that it might be helpful in the other regions of the world.
+- Integrate the bot with financial services, like bank APIs, for real-time expense tracking and account balance updates.
+- Implement a reminder system to notify users of recurring expenses, upcoming bills, or when they need to settle debts.
## Contributors
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
new file mode 100644
index 000000000..be7f6891f
--- /dev/null
+++ b/TROUBLESHOOTING.md
@@ -0,0 +1,23 @@
+# Troubleshooting Guide
+
+## How to Access Your Gmail Account with an 'App Password'
+
+If you're having trouble accessing your Gmail account, you can use Google's 'App Password' solution to resolve the issue. Follow these steps to generate and use an app password:
+
+1. **Enable Two-Step Verification:**
+ - Go to your Google Account settings by visiting [Google Account](https://myaccount.google.com/).
+ - In the "Security" section, locate and select "Two-step verification."
+ - Follow the on-screen instructions to set up two-step verification for your account. This adds an extra layer of security.
+
+2. **Create an App Password:**
+ - After enabling two-step verification, navigate to your Google Account's security settings.
+ - In the "Signing in to Google" section, choose "App Passwords."
+ - Select "Mail" and "Other (Custom name)" from the respective dropdown menus.
+ - Click the "Generate" button.
+ - Google will provide you with a 16-character App Password. This password is a one-time use code that allows you to access your Gmail account without revealing your actual account password.
+
+3. **Use the App Password:**
+ - When configuring email settings or applications, use the same settings as you would for sending emails from your Gmail account.
+ - However, replace your regular password with the generated 16-character app password.
+ - This app password should be entered wherever you are prompted for your email password.
+
diff --git a/code/add.py b/code/add.py
index 906956ad1..97342c3a3 100644
--- a/code/add.py
+++ b/code/add.py
@@ -6,8 +6,6 @@
option = {}
-# === Documentation of add.py ===
-
def run(message, bot):
"""
@@ -17,42 +15,69 @@ def run(message, bot):
It takes 2 arguments for processing - message which is the message from the user,
and bot which is the telegram bot object from the main code.py function.
"""
- helper.read_json()
+ user_list=helper.read_json()
chat_id = message.chat.id
- option.pop(chat_id, None) # remove temp choice
+ owed_by =[]
+ # option.pop(chat_id, None) # remove temp choice
+
+ if str(chat_id) not in user_list:
+ user_list[str(chat_id)] = helper.createNewUserRecord(message)
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- markup.row_width = 2
- m = bot.send_message(chat_id, "Do you want to add a new category? Y/N")
- bot.register_next_step_handler(m, post_user_def_category, bot)
+ markup.row_width = len(user_list[str(chat_id)]["users"])
+ for c in user_list[str(chat_id)]["users"]:
+ markup.add(c)
+ m = bot.send_message(chat_id, "Select who paid for the Expense",reply_markup=markup)
+ bot.register_next_step_handler(m, select_user, bot,owed_by,user_list,None)
-def post_user_def_category(message, bot):
- markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- markup.row_width = 2
+def select_user(message,bot,owed_by,user_list,paid_by):
chat_id = message.chat.id
- if str(message.text) == "Y" or str(message.text) == "y":
- message1 = bot.send_message(chat_id, "Please enter your category")
- bot.register_next_step_handler(message1, post_append_spend, bot)
+ text_m = message.text
+ remaining_users = [item for item in user_list[str(chat_id)]["users"] if item not in owed_by]
+ if len(remaining_users)==0:
+ post_append_spend(message,bot,owed_by,user_list,paid_by)
else:
- for c in helper.getSpendCategories():
- markup.add(c)
- msg = bot.reply_to(message, "Select Category", reply_markup=markup)
- bot.register_next_step_handler(msg, post_category_selection, bot)
+ if text_m in user_list[str(chat_id)]["users"]:
+ paid_by = text_m
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+
+ for c in remaining_users:
+ markup.add(c)
+ m = bot.send_message(chat_id, "Select who shares the Expense",reply_markup=markup)
+ bot.register_next_step_handler(m, add_shared_user, bot,owed_by,user_list,paid_by)
+
+def add_shared_user(message,bot,owed_by,user_list,paid_by):
+ chat_id = message.chat.id
+ user = message.text
+ if user in user_list[str(chat_id)]["users"]:
+ owed_by.append(user)
+ else:
+ pass
+ choice = bot.reply_to(message, "Do you want to add more user to share the expense? Y/N")
+ bot.register_next_step_handler(choice, user_choice, bot, owed_by,user_list,paid_by)
+
+def user_choice(message, bot,owed_by, user_list,paid_by):
+ Choice = message.text
+ if Choice == "Y" or Choice == 'y':
+ select_user(message,bot,owed_by,user_list,paid_by)
+ elif Choice == "N" or Choice == 'n':
+ post_append_spend(message,bot,owed_by,user_list,paid_by)
-def post_append_spend(message, bot):
+def post_append_spend(message, bot,owed_by,user_list,paid_by):
+ chat_id = message.chat.id
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
markup.row_width = 2
- selected_category = message.text
- helper.spend_categories.append(selected_category)
+ m = bot.send_message(chat_id, "Select a category")
for c in helper.getSpendCategories():
- markup.add(c)
+ markup.add(c)
msg = bot.reply_to(message, "Select Category", reply_markup=markup)
- bot.register_next_step_handler(msg, post_category_selection, bot)
+ bot.register_next_step_handler(msg, post_category_selection, bot,owed_by,paid_by,user_list)
-def post_category_selection(message, bot):
+def post_category_selection(message, bot,owed_by,paid_by,user_list):
"""
post_category_selection(message, bot): It takes 2 arguments for processing -
message which is the message from the user, and bot which is the telegram bot object
@@ -79,7 +104,7 @@ def post_category_selection(message, bot):
),
)
bot.register_next_step_handler(
- message, post_amount_input, bot, selected_category
+ message, post_amount_input, bot, selected_category,owed_by,paid_by,user_list
)
except Exception as e:
logging.exception(str(e))
@@ -97,7 +122,7 @@ def post_category_selection(message, bot):
bot.send_message(chat_id, display_text)
-def post_amount_input(message, bot, selected_category):
+def post_amount_input(message, bot, selected_category,owed_by,paid_by,user_list):
"""
post_amount_input(message, bot): It takes 2 arguments for processing -
message which is the message from the user, and bot which is the telegram bot
@@ -106,14 +131,8 @@ def post_amount_input(message, bot, selected_category):
calls add_user_record to store it.
"""
try:
- print("---------------------------------------------------")
-
chat_id = message.chat.id
- print(chat_id)
amount_entered = message.text
- print("0000000000000000000000000000000000000000000000000")
- print(amount_entered)
- print(selected_category)
amount_value = helper.validate_entered_amount(amount_entered) # validate
if amount_value == 0: # cannot be $0 spending
raise Exception("Spent amount has to be a non-zero number.")
@@ -130,7 +149,7 @@ def post_amount_input(message, bot, selected_category):
helper.write_json(
add_user_record(
- chat_id, "{},{},{}".format(date_str, category_str, amount_str)
+ user_list,message,chat_id, "{},{},{}".format(date_str, category_str, amount_str),amount_value,owed_by,paid_by
)
)
@@ -140,30 +159,35 @@ def post_amount_input(message, bot, selected_category):
amount_str, category_str, date_str
),
)
- helper.display_remaining_budget(message, bot, selected_category)
except Exception as e:
logging.exception(str(e))
bot.reply_to(message, "Oh no. " + str(e))
-def add_user_record(chat_id, record_to_be_added):
+def add_user_record(user_list,message,chat_id, record_to_be_added,amount_value,owed_by, paid_by):
"""
add_user_record(chat_id, record_to_be_added): Takes 2 arguments -
chat_id or the chat_id of the user's chat, and record_to_be_added which
is the expense record to be added to the store. It then stores this expense record in the store.
"""
- user_list = helper.read_json()
- print("!" * 5)
- print("before")
- print(user_list)
- print("!" * 5)
if str(chat_id) not in user_list:
- user_list[str(chat_id)] = helper.createNewUserRecord()
+ user_list[str(chat_id)] = helper.createNewUserRecord(message)
+ owed_amount = float(amount_value)/len(set(owed_by))
+ if "data" in user_list[str(chat_id)]:
+ user_list[str(chat_id)]["data"].append(record_to_be_added)
+ else:
+ user_list[str(chat_id)]["data"] = [record_to_be_added]
+ user_list[str(chat_id)]["owed"][paid_by] += float(amount_value)
+ for user in set(owed_by):
+ if user == paid_by:
+ user_list[str(chat_id)]["owed"][paid_by] -= owed_amount
+ elif paid_by in user_list[str(chat_id)]["owing"][user].keys():
+ user_list[str(chat_id)]["owing"][user][paid_by] += owed_amount
+ else:
+ user_list[str(chat_id)]["owing"][user][paid_by] = owed_amount
+ record_to_be_added+=",{},{}".format(paid_by,' & '.join(owed_by))
+ user_list[str(chat_id)]["csv_data"].append(record_to_be_added)
+ print("####",user_list)
+ return user_list
- user_list[str(chat_id)]["data"].append(record_to_be_added)
- print("!" * 5)
- print("after")
- print(user_list)
- print("!" * 5)
- return user_list
diff --git a/code/add_category.py b/code/add_category.py
new file mode 100644
index 000000000..10c8fb304
--- /dev/null
+++ b/code/add_category.py
@@ -0,0 +1,55 @@
+import helper
+import logging
+from telebot import types
+from datetime import datetime
+
+
+option = {}
+
+# === Documentation of add.py ===
+
+
+def run(message, bot):
+ """
+ run(message, bot): This is the main function used to implement the add feature.
+ It pop ups a menu on the bot asking the user to choose their expense category,
+ after which control is given to post_category_selection(message, bot) for further proccessing.
+ It takes 2 arguments for processing - message which is the message from the user,
+ and bot which is the telegram bot object from the main code.py function.
+ """
+ helper.read_json()
+ chat_id = message.chat.id
+ option.pop(chat_id, None) # remove temp choice
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ message1 = bot.send_message(chat_id, "Please enter your category")
+ bot.register_next_step_handler(message1, post_append_spend, bot)
+
+
+
+def post_append_spend(message, bot):
+ chat_id = message.chat.id
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ selected_category = message.text
+ if selected_category.lower() in [x.lower() for x in helper.spend_categories]:
+ bot.send_message(
+ chat_id, "Category already exists", reply_markup=types.ReplyKeyboardRemove()
+ )
+ message1 = bot.send_message(chat_id, "Please enter a new category")
+ bot.register_next_step_handler(message1, post_append_spend, bot)
+
+ else:
+ helper.spend_categories.append(selected_category)
+ user_list = helper.read_json()
+ user_list[str(chat_id)]["budget"]["category"][selected_category] = '0'
+ helper.write_json(user_list)
+ for c in helper.getSpendCategories():
+ markup.add(c)
+ bot.send_message(
+ chat_id,
+ "The following category has been added: {} ".format(
+ selected_category
+ ),
+ )
+
diff --git a/code/add_user.py b/code/add_user.py
new file mode 100644
index 000000000..a97c639f4
--- /dev/null
+++ b/code/add_user.py
@@ -0,0 +1,73 @@
+import helper
+import logging
+from telebot import types
+
+# Initialize a dictionary to store registered users
+registered_users = {}
+# user_list=helper.read_json()
+def register_people(message, bot,user_list):
+ chat_id = message.chat.id
+ if str(chat_id) not in user_list:
+ user_list[str(chat_id)] = helper.createNewUserRecord(message)
+ if "users" in user_list[str(chat_id)].keys():
+ registered_users={chat_id : user_list[str(chat_id)]["users"]}
+ else:
+ registered_users = {}
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ msg = bot.send_message(chat_id, "Enter the name of the person you want to register:")
+ bot.register_next_step_handler(msg, add_person, bot,registered_users,user_list)
+
+def add_person(message, bot,registered_users,user_list):
+ chat_id = message.chat.id
+ name = message.text
+
+ # Check if the name is unique for this chat_id
+ if chat_id in registered_users and name in registered_users[chat_id]:
+ bot.send_message(chat_id, f"{name} is already registered.")
+ else:
+ if chat_id not in registered_users.keys():
+ registered_users[chat_id] = []
+
+ registered_users[chat_id].append(name)
+
+ bot.send_message(chat_id, f"{name} has been registered successfully!")
+
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ markup.add("Register Another Person", "Finish Registration")
+ msg = bot.send_message(chat_id, "What would you like to do next?", reply_markup=markup)
+
+ bot.register_next_step_handler(msg, handle_registration_choice, bot,registered_users,user_list)
+
+def handle_registration_choice(message, bot,registered_users,user_list):
+ chat_id = message.chat.id
+ choice = message.text
+
+ if choice == "Register Another Person":
+ msg = bot.send_message(chat_id, "Enter the name of the person you want to register:")
+ bot.register_next_step_handler(msg, add_person, bot,registered_users,user_list)
+ elif choice == "Finish Registration":
+ # Display the names of registered users
+ if chat_id in registered_users:
+ users = registered_users[chat_id]
+ user_list[str(chat_id)]["users"]=users
+ for user in users:
+ if str(chat_id) in user_list:
+ if "owed" in user_list[str(chat_id)]:
+ user_list[str(chat_id)]["owed"][user] = 0
+ else:
+ user_list[str(chat_id)]["owed"] = {user: 0}
+ if "owing" in user_list[str(chat_id)]:
+ user_list[str(chat_id)]["owing"][user] = {}
+ else:
+ user_list[str(chat_id)]["owing"] = {user: {}}
+ else:
+ user_list[str(chat_id)] = {"owed": {user: 0},"owing": {user: {}}}
+ helper.write_json(user_list)
+ if users:
+ bot.send_message(chat_id, "Registered Users:\n" + '\n'.join(registered_users[chat_id]))
+ else:
+ bot.send_message(chat_id, "No users registered yet.")
+ else:
+ bot.send_message(chat_id, "Invalid choice. Please select a valid option.")
diff --git a/code/budget.py b/code/budget.py
index 0bd1359ae..f29e57430 100644
--- a/code/budget.py
+++ b/code/budget.py
@@ -1,21 +1,11 @@
import helper
+from telebot import types
+import logging
import budget_view
import budget_update
import budget_delete
-import logging
-from telebot import types
-
-# === Documentation of budget.py ===
-
def run(message, bot):
- """
- run(message, bot): This is the main function used to implement the budget feature.
- It pop ups a menu on the bot asking the user to choose to add, remove or display a budget,
- after which control is given to post_operation_selection(message, bot) for further proccessing.
- It takes 2 arguments for processing - message which is the message from the user, and bot which is the
- telegram bot object from the main code.py function.
- """
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
options = helper.getBudgetOptions()
markup.row_width = 2
@@ -24,29 +14,29 @@ def run(message, bot):
msg = bot.reply_to(message, "Select Operation", reply_markup=markup)
bot.register_next_step_handler(msg, post_operation_selection, bot)
-
def post_operation_selection(message, bot):
- """
- post_operation_selection(message, bot): It takes 2 arguments for processing - message which
- is the message from the user, and bot which is the telegram bot object from the
- run(message, bot): function in the budget.py file. Depending on the action chosen by the user,
- it passes on control to the corresponding functions which are all located in different files.
- """
try:
chat_id = message.chat.id
+ user_list = helper.read_json()
op = message.text
options = helper.getBudgetOptions()
+
if op not in options.values():
- bot.send_message(
- chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
- )
+ bot.send_message(chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove())
raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op))
+ if str(chat_id) not in user_list:
+ # Initialize the user's data with an empty budget dictionary
+ user_list[str(chat_id)] = helper.createNewUserRecord(message)
+ if op == options["add"]:
+ budget_update.run(message, bot)
if op == options["update"]:
budget_update.run(message, bot)
elif op == options["view"]:
budget_view.run(message, bot)
elif op == options["delete"]:
budget_delete.run(message, bot)
+
+ helper.write_json(user_list)
+
except Exception as e:
- # print("hit exception")
- helper.throw_exception(e, message, bot, logging)
+ helper.throw_exception(e, message, bot, logging)
\ No newline at end of file
diff --git a/code/budget_delete.py b/code/budget_delete.py
index a4462563c..33a9e508c 100644
--- a/code/budget_delete.py
+++ b/code/budget_delete.py
@@ -1,22 +1,21 @@
import helper
-
-# === Documentation of budget_delete.py ===
-
+from telebot import types
+import logging
def run(message, bot):
- """
- run(message, bot): This is the main function used to implement the budget delete feature.
- It takes 2 arguments for processing - message which is the message from the user, and bot
- which is the telegram bot object from the main code.py function. It gets the user's chat ID
- from the message object, and reads all user data through the read_json method from the helper module.
- It then proceeds to empty the budget data for the particular user based on the user ID provided from the UI.
- It returns a simple message indicating that this operation has been done to the UI.
- """
chat_id = message.chat.id
user_list = helper.read_json()
- print(user_list)
- if str(chat_id) in user_list:
- user_list[str(chat_id)]["budget"]["overall"] = None
- user_list[str(chat_id)]["budget"]["category"] = None
+
+ if str(chat_id) not in user_list:
+ bot.send_message(chat_id, "You don't have budget data to delete.")
+ else:
+ if "budget" in user_list[str(chat_id)]:
+ # The 'budget' dictionary exists; you can proceed with deleting it
+ user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}}
+ else:
+ bot.send_message(chat_id, "No budget data to delete.")
+
helper.write_json(user_list)
- bot.send_message(chat_id, "Budget deleted!")
+ bot.send_message(chat_id, "Budget data deleted successfully.")
+
+ helper.write_json(user_list)
\ No newline at end of file
diff --git a/code/budget_update.py b/code/budget_update.py
index df8041f4a..4d2b4be09 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -1,217 +1,138 @@
+#budget_update.py
+
import helper
-import logging
-import budget_view
from telebot import types
-
-# === Documentation of budget_update.py ===
-
+import logging
def run(message, bot):
- """
- run(message, bot): This is the main function used to implement the budget add/update features.
- It takes 2 arguments for processing - message which is the message from the user, and bot which
- is the telegram bot object from the main code.py function.
- """
chat_id = message.chat.id
- if helper.isOverallBudgetAvailable(chat_id):
- update_overall_budget(chat_id, bot)
- elif helper.isCategoryBudgetAvailable(chat_id):
- update_category_budget(message, bot)
- else:
- markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- options = helper.getBudgetTypes()
- markup.row_width = 2
- for c in options.values():
- markup.add(c)
- msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup)
- bot.register_next_step_handler(msg, post_type_selection, bot)
-
-
-def post_type_selection(message, bot):
- """
- post_type_selection(message, bot): It takes 2 arguments for processing - message
- which is the message from the user, and bot which is the telegram bot object.
- This function takes input from the user, making them choose which type of budget they
- would like to create - category-wise or overall, and then calls the corresponding functions for further processing.
- """
+ user_list = helper.read_json()
+
+ if str(chat_id) not in user_list:
+ # Initialize the user's data with an empty budget dictionary
+ user_list[str(chat_id)] = {"budget": {"overall": None, "category": {}}}
+
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ budget_types = helper.getBudgetTypes()
+ markup.row_width = 2
+ for btype in budget_types.values():
+ markup.add(btype)
+ msg = bot.reply_to(message, "Select Budget Type", reply_markup=markup)
+ bot.register_next_step_handler(msg, set_budget_type, bot, user_list)
+
+def set_budget_type(message, bot, user_list):
try:
chat_id = message.chat.id
- op = message.text
+ budget_type = message.text
options = helper.getBudgetTypes()
- if op not in options.values():
- bot.send_message(
- chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
- )
- raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op))
- if op == options["overall"]:
- update_overall_budget(chat_id, bot)
- elif op == options["category"]:
- update_category_budget(message, bot)
+
+ if budget_type not in options.values():
+ bot.send_message(chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove())
+ raise Exception('Sorry I don\'t recognise this budget type "{}"!'.format(budget_type))
+
+ if budget_type == options["overall"]:
+ set_overall_budget(message, bot, user_list)
+ elif budget_type == options["category"]:
+ set_category_budget(message, bot, user_list)
+
except Exception as e:
helper.throw_exception(e, message, bot, logging)
+def set_overall_budget(message, bot, user_list):
+ chat_id = message.chat.id
-def update_overall_budget(chat_id, bot):
- """
- update_overall_budget(message, bot): It takes 2 arguments for processing - message which is the
- message from the user, and bot which is the telegram bot object. This function is called when the
- user wants to either create a new overall budget or update an existing one. It checks if there is an
- existing budget through the helper module's isOverallBudgetAvailable function and if so, displays this
- along with the prompt for the new (to be updated) budget, or just asks for the new budget. It passes control
- to the post_overall_amount_input function in the same file.
- """
- if helper.isOverallBudgetAvailable(chat_id):
- currentBudget = helper.getOverallBudget(chat_id)
- msg_string = "Current Budget is ${}\n\nHow much is your new monthly budget? \n(Enter numeric values only)"
- message = bot.send_message(chat_id, msg_string.format(currentBudget))
- else:
- message = bot.send_message(
- chat_id, "How much is your monthly budget? \n(Enter numeric values only)"
- )
- bot.register_next_step_handler(message, post_overall_amount_input, bot)
-
-
-def post_overall_amount_input(message, bot):
- """
- update_overall_budget(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object.
- This function is called when the user wants to either create a new overall budget or
- update an existing one. It checks if there is an existing budget through the helper module's
- isOverallBudgetAvailable function and if so, displays this along with the prompt for the new
- (to be updated) budget, or just asks for the new budget. It passes control to the post_overall_amount_input
- function in the same file.
- """
+ if str(chat_id) not in user_list:
+ bot.send_message(chat_id, "You don't have budget data to set.")
+ return
+
try:
- chat_id = message.chat.id
- amount_value = helper.validate_entered_amount(message.text)
- if amount_value == 0:
- raise Exception("Invalid amount.")
- user_list = helper.read_json()
- if str(chat_id) not in user_list:
- user_list[str(chat_id)] = helper.createNewUserRecord()
- user_list[str(chat_id)]["budget"]["overall"] = amount_value
+ msg = bot.reply_to(message, "Enter the Overall Budget", reply_markup=types.ReplyKeyboardRemove())
+ bot.register_next_step_handler(msg, save_overall_budget, bot, user_list)
+ except Exception as e:
+ helper.throw_exception(e, message, bot, logging)
+
+def save_overall_budget(message, bot, user_list):
+ chat_id = message.chat.id
+ overall_budget = message.text
+
+ if not overall_budget:
+ bot.send_message(chat_id, "Budget not set. Please enter a valid budget.")
+ return
+
+ if str(chat_id) not in user_list:
+ bot.send_message(chat_id, "You don't have budget data to set.")
+ return
+
+ try:
+ # Ensure the 'budget' dictionary exists
+ if "budget" not in user_list[str(chat_id)]:
+ user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}}
+
+ # Set the overall budget in the user's data
+ user_list[str(chat_id)]["budget"]["overall"] = float(overall_budget)
+
helper.write_json(user_list)
- bot.send_message(chat_id, "Budget Updated!")
- budget_view.display_overall_budget(message, bot)
- return user_list
+
+ bot.send_message(chat_id, f"Overall Budget set to ${str(overall_budget)}")
except Exception as e:
helper.throw_exception(e, message, bot, logging)
+def set_category_budget(message, bot, user_list):
+ chat_id = message.chat.id
-def update_category_budget(message, bot):
- """
- update_category_budget(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object.
- This function is called in case the user decides to choose category-wise budgest in the run or
- post_type_selection stages. It gets the spend categories from the helper module's getSpendCategories
- and displays them to the user. It then passes control on to the post_category_selection function.
- """
- markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- categories = helper.getSpendCategories()
- markup.row_width = 2
- for c in categories:
- markup.add(c)
- msg = bot.reply_to(message, "Select Category", reply_markup=markup)
- bot.register_next_step_handler(msg, post_category_selection, bot)
-
-
-def post_category_selection(message, bot):
- """
- post_category_selection(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object.
- Based on the category chosen by the user, the bot checks if these are part of the pre-defined
- categories in helper.getSpendCategories(), else it throws an exception. If there is a budget
- already existing for the category, it identifies this case through helper.isCategoryBudgetByCategoryAvailable
- and shares this information with the user. If not, it simply proceeds. In either case, it then asks for the
- new/updated budget amount. It passes control onto post_category_amount_input.
- """
+ if str(chat_id) not in user_list:
+ bot.send_message(chat_id, "You don't have budget data to set.")
+ return
+
try:
- chat_id = message.chat.id
- selected_category = message.text
categories = helper.getSpendCategories()
- if selected_category not in categories:
- bot.send_message(
- chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
- )
- raise Exception(
- 'Sorry I don\'t recognise this category "{}"!'.format(selected_category)
- )
- if helper.isCategoryBudgetByCategoryAvailable(chat_id, selected_category):
- currentBudget = helper.getCategoryBudgetByCategory(
- chat_id, selected_category
- )
- msg_string = "Current monthly budget for {} is {}\n\nEnter monthly budget for {}\n(Enter numeric values only)"
- message = bot.send_message(
- chat_id,
- msg_string.format(selected_category, currentBudget, selected_category),
- )
- else:
- message = bot.send_message(
- chat_id,
- "Enter monthly budget for " + selected_category + "\n(Enter numeric values only)",
- )
- bot.register_next_step_handler(
- message, post_category_amount_input, bot, selected_category
- )
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.add(*categories)
+ msg = bot.reply_to(message, "Select a Category for Budget", reply_markup=markup)
+ bot.register_next_step_handler(msg, set_category_budget_amount, bot, user_list)
except Exception as e:
helper.throw_exception(e, message, bot, logging)
+def set_category_budget_amount(message, bot, user_list):
+ chat_id = message.chat.id
+ category = message.text
-def post_category_amount_input(message, bot, category):
- """
- post_category_amount_input(message, bot, category): It takes 2 arguments for
- processing - message which is the message from the user, and bot which is the telegram
- bot object, and the category chosen by the user.
- """
+ if category not in helper.getSpendCategories():
+ bot.send_message(chat_id, "Invalid category.", reply_markup=types.ReplyKeyboardRemove())
+ return
+
try:
- chat_id = message.chat.id
- amount_value = helper.validate_entered_amount(message.text)
- if amount_value == 0:
- raise Exception("Invalid amount.")
- user_list = helper.read_json()
- if str(chat_id) not in user_list:
- user_list[str(chat_id)] = helper.createNewUserRecord()
- if user_list[str(chat_id)]["budget"]["category"] is None:
- user_list[str(chat_id)]["budget"]["category"] = {}
- user_list[str(chat_id)]["budget"]["category"][category] = amount_value
- helper.write_json(user_list)
- message = bot.send_message(
- chat_id, "Budget for " + category + " is now: $" + amount_value
- )
- post_category_add(message, bot)
-
+ msg = bot.reply_to(message, f"Enter Budget for {category}", reply_markup=types.ReplyKeyboardRemove())
+ bot.register_next_step_handler(msg, save_category_budget, bot, user_list, category)
except Exception as e:
helper.throw_exception(e, message, bot, logging)
+def save_category_budget(message, bot, user_list, category):
+ chat_id = message.chat.id
+ category_budget = message.text
-def post_category_add(message, bot):
- """
- post_category_add(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object.
- This exists in case the user wants to add a category-wise budget to another category after adding
- it for one category. It prompts the user to choose an option from helper.getUpdateOptions().values() and
- passes control to post_option_selection to either continue or exit the add/update feature.
- """
- markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- options = helper.getUpdateOptions().values()
- markup.row_width = 2
- for c in options:
- markup.add(c)
- msg = bot.reply_to(message, "Select Option", reply_markup=markup)
- bot.register_next_step_handler(msg, post_option_selection, bot)
-
-
-def post_option_selection(message, bot):
- """
- post_option_selection(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object.
- It takes the category chosen by the user from the message object. If the message is "continue",
- then it runs update_category_budget (above) allowing the user to get into the add/update process again.
- Otherwise, it exits the feature.
- """
- print("here")
- selected_option = message.text
- options = helper.getUpdateOptions()
- print("here")
- if selected_option == options["continue"]:
- update_category_budget(message, bot)
+ if not category_budget:
+ bot.send_message(chat_id, "Budget not set. Please enter a valid budget.")
+ return
+
+ if str(chat_id) not in user_list:
+ bot.send_message(chat_id, "You don't have budget data to set.")
+ return
+
+ try:
+ # Ensure the 'budget' dictionary exists
+ if "budget" not in user_list[str(chat_id)]:
+ user_list[str(chat_id)]["budget"] = {"overall": None, "category": {}}
+
+ # Ensure the category budget dictionary exists
+ if "category" not in user_list[str(chat_id)]["budget"]:
+ user_list[str(chat_id)]["budget"]["category"] = {}
+
+ # Set the category budget in the user's data
+ user_list[str(chat_id)]["budget"]["category"][category] = float(category_budget)
+
+ helper.write_json(user_list)
+
+ bot.send_message(chat_id, f"Budget for {category} set to ${str(category_budget)}")
+ except Exception as e:
+ helper.throw_exception(e, message, bot, logging)
\ No newline at end of file
diff --git a/code/budget_view.py b/code/budget_view.py
index 077f8327b..1360c3854 100644
--- a/code/budget_view.py
+++ b/code/budget_view.py
@@ -1,23 +1,8 @@
-import graphing
import helper
import logging
-import os
-
-# === Documentation of budget_view.py ===
-
def run(message, bot):
- """
- run(message, bot): This is the main function used to implement the budget feature.
- It takes 2 arguments for processing - message which is the message from the user, and bot which
- is the telegram bot object from the main code.py function. Depending on whether the user has configured
- an overall budget or a category-wise budget, this functions checks for either case using the helper
- module's isOverallBudgetAvailable and isCategoryBudgetAvailable functions and passes control on the
- respective functions(listed below). If there is no budget configured an exception is raised and the user
- is given a message indicating that there is no budget configured.
- """
try:
- print("here")
chat_id = message.chat.id
if helper.isOverallBudgetAvailable(chat_id):
display_overall_budget(message, bot)
@@ -30,29 +15,17 @@ def run(message, bot):
except Exception as e:
helper.throw_exception(e, message, bot, logging)
-
def display_overall_budget(message, bot):
- """
- display_overall_budget(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot
- object from the run(message, bot): in the same file. It gets the budget for the
- user based on their chat ID using the helper module and returns the same through the bot to the Telegram UI.
- """
chat_id = message.chat.id
data = helper.getOverallBudget(chat_id)
- bot.send_message(chat_id, "Overall Budget: $" + data)
+ if data is not None:
+ data = str(data) # Convert the float to a string
+ bot.send_message(chat_id, "Overall Budget: $" + str(data))
def display_category_budget(message, bot):
- """
- display_category_budget(message, bot): It takes 2 arguments for processing -
- message which is the message from the user, and bot which is the telegram bot object
- from the run(message, bot): in the same file. It gets the category-wise budget for the
- user based on their chat ID using the helper module.It then processes it into a string
- format suitable for display, and returns the same through the bot to the Telegram UI.
- """
chat_id = message.chat.id
data = helper.getCategoryBudget(chat_id)
- graphing.viewBudget(data)
- bot.send_photo(chat_id, photo=open("budget.png", "rb"))
- os.remove("budget.png")
+ if data is not None:
+ formatted_data = "\n".join([f"{category}: ${budget}" for category, budget in data.items()])
+ bot.send_message(chat_id, "Category-Wise Budgets:\n" + formatted_data)
diff --git a/code/code.py b/code/code.py
index 10bc43540..0bc265299 100644
--- a/code/code.py
+++ b/code/code.py
@@ -11,7 +11,13 @@
import estimate
import delete
import add
+import add_category
+import delete_expense
+import send_mail
import budget
+import csvfile
+import add_user
+import delete_user
from datetime import datetime
from jproperties import Properties
@@ -19,7 +25,7 @@
with open("user.properties", "rb") as read_prop:
configs.load(read_prop)
-
+user_list = helper.read_json()
api_token = str(configs.get("api_token").data)
bot = telebot.TeleBot(api_token)
@@ -80,7 +86,6 @@ def help(m):
commands = helper.getCommands()
for c in commands:
message += "/" + c + ", "
- # message += commands[c] + "\n\n"
message += "\nUse /menu for detailed instructions about these commands."
bot.send_message(chat_id, message)
@@ -96,7 +101,8 @@ def faq(m):
('"What does this bot do?"\n'
">> DollarBot lets you manage your expenses so you can always stay on top of them! \n\n"
'"How can I add an epxense?" \n'
- ">> Type /add, then select a category to type the expense. \n\n"
+ ">> Type /add_category, then add a category for the expense. \n\n"
+ ">> Type /add_category, then select a category to type the expense. \n\n"
'"Can I see history of my expenses?" \n'
">> Yes! Use /display to get a graphical display, or /history to view detailed summary.\n\n"
'"I added an incorrect expense. How can I edit it?"\n'
@@ -115,9 +121,14 @@ def start_and_menu_command(m):
bot offers and the corresponding commands to be run from the Telegram UI to use these features.
Commands used to run this: commands=['start', 'menu']
"""
- helper.read_json()
- global user_list
+ global user_list
+ user_list = helper.read_json()
chat_id = m.chat.id
+ print(user_list)
+ if str(chat_id) not in user_list:
+ user_list[str(chat_id)] = helper.createNewUserRecord(m)
+
+
# print('receieved start or menu command.')
# text_into = "Welcome to the Dollar Bot!"
@@ -152,6 +163,24 @@ def command_add(message):
add.run(message, bot)
+@bot.message_handler(commands=["add_user"])
+def command_add_user(message):
+ add_user.register_people(message,bot,user_list)
+
+@bot.message_handler(commands=["delete_user"])
+def command_delete_user(message):
+ # Call the delete_user function from the delete_user module
+ registered_users=user_list[str(message.chat.id)]["users"]
+ delete_user.delete_user(message, bot, user_list)
+
+@bot.message_handler(commands=["add_category"])
+def command_add_category(message):
+ """
+ command_add(message) Takes 1 argument message which contains the message from
+ the user along with the chat ID of the user chat. It then calls add.py to run to execute
+ the add functionality. Commands used to run this: commands=['add']
+ """
+ add_category.run(message, bot)
# function to fetch expenditure history of the user
@@ -165,6 +194,15 @@ def command_pdf(message):
pdf.run(message, bot)
+@bot.message_handler(commands=["csv"])
+def command_csv(message):
+ """
+ command_history(message): Takes 1 argument message which contains the message from
+ the user along with the chat ID of the user chat. It then calls csv.py to run to execute
+ the add functionality. Commands used to run this: commands=['csv']
+ """
+ csvfile.run(message, bot)
+
# function to fetch expenditure history of the user
@bot.message_handler(commands=["history"])
def command_history(message):
@@ -214,11 +252,25 @@ def command_delete(message):
"""
delete.run(message, bot)
+# handles "/delete_expense" command
+@bot.message_handler(commands=["delete_expense"])
+def command_delete(message):
+ """
+ command_delete(message): Takes 1 argument message which contains the message from the user
+ along with the chat ID of the user chat. It then calls delete_expense.py to run to execute the add functionality.
+ Commands used to run this: commands=['display']
+ """
+ delete_expense.run(message, bot)
+
@bot.message_handler(commands=["budget"])
def command_budget(message):
budget.run(message, bot)
+@bot.message_handler(commands=["send_mail"])
+def command_send_mail(message):
+ send_mail.run(message, bot)
+
# not used
diff --git a/code/csvfile.py b/code/csvfile.py
new file mode 100644
index 000000000..1722eaf48
--- /dev/null
+++ b/code/csvfile.py
@@ -0,0 +1,59 @@
+import helper
+import logging
+from matplotlib import pyplot as plt
+from telebot import types
+import csv
+
+# === Documentation of pdf.py ===
+
+
+def run(message, bot):
+ try:
+ user_list=helper.read_json()
+ chat_id = message.chat.id
+ user_history = helper.getUserHistory(chat_id)
+ print('User-history--> ',user_history)
+ if user_history != None:
+ data = user_list[str(chat_id)]['csv_data']
+ message = "Alright. I just created a csv file of your expense history!"
+ bot.send_message(chat_id, message)
+ csv_file = 'expense_report.csv'
+ # Open the CSV file for writing
+ with open(csv_file, 'w', newline='') as file:
+ writer = csv.writer(file)
+
+ # Write the header row
+ writer.writerow(['Date', 'Category', 'Amount', 'Payer', 'Participants'])
+
+ # Write the data from the list
+ for item in data:
+ parts = item.split(',')
+ writer.writerow(parts)
+ bot.send_document(chat_id, open("expense_report.csv", "rb"))
+ print("CSV generated successfully.")
+ #issue 15 - modified the format of pdf document - start
+
+
+ #Issue 3 - added the else condition - start
+ else:
+ message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf."
+ bot.send_message(chat_id, message)
+
+ display_text = ""
+ commands = helper.getCommands()
+ for (
+ c
+ ) in (
+ commands
+ ): # generate help text out of the commands dictionary defined at the top
+ display_text += "/" + c + ": "
+ display_text += commands[c] + "\n"
+ bot.send_message(chat_id, "Please select a menu option from below:")
+ bot.send_message(chat_id, display_text)
+ #Issue 3 - added the else condition - end
+
+ except Exception as e:
+ logging.exception(str(e))
+ bot.send_message(message, "Oops!" + str(e))
+
+
diff --git a/code/delete.py b/code/delete.py
index 6b7f9f2c1..e8455542f 100644
--- a/code/delete.py
+++ b/code/delete.py
@@ -12,12 +12,11 @@ def run(message, bot):
data saved in myDollarBot i.e their chat ID has been logged before, run calls the deleteHistory(chat_id):
to remove it. Then it ensures this removal is saved in the datastore.
"""
- global user_list
chat_id = message.chat.id
delete_history_text = ""
user_list = helper.read_json()
if str(chat_id) in user_list:
- helper.write_json(deleteHistory(chat_id))
+ helper.write_json(deleteHistory(chat_id,user_list))
delete_history_text = "History has been deleted!"
else:
delete_history_text = "No records there to be deleted. Start adding your expenses to keep track of your spendings!"
@@ -25,12 +24,11 @@ def run(message, bot):
# function to delete a record
-def deleteHistory(chat_id):
+def deleteHistory(chat_id,user_list):
"""
deleteHistory(chat_id): It takes 1 argument for processing - chat_id which is the
chat_id of the user whose data is to deleted from the user list. It removes this entry from the user list.
"""
- global user_list
if str(chat_id) in user_list:
del user_list[str(chat_id)]
return user_list
diff --git a/code/delete_expense.py b/code/delete_expense.py
new file mode 100644
index 000000000..5e1528fca
--- /dev/null
+++ b/code/delete_expense.py
@@ -0,0 +1,114 @@
+import helper
+from telebot import types
+import history
+# === Documentation of delete_expense.py ===
+
+def run(m, bot):
+ """
+ run(message, bot): This is the main function used to implement the delete feature.
+ It takes 2 arguments for processing - message which is the message from the user, and
+ bot which is the telegram bot object from the main code.py function. It gets the details
+ for the expense to be edited from here and passes control onto edit2(m, bot): for further processing.
+ """
+ chat_id = m.chat.id
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ for c in helper.getUserHistory(chat_id):
+ expense_data = c.split(",")
+ str_date = "Date=" + expense_data[0]
+ str_category = ",\t\tCategory=" + expense_data[1]
+ str_amount = ",\t\tAmount=$" + expense_data[2]
+ markup.add(str_date + str_category + str_amount)
+ info = bot.reply_to(m, "Select expense to be deleted:", reply_markup=markup)
+ bot.register_next_step_handler(info, select_category_to_be_deleted, bot)
+
+def select_category_to_be_deleted(m, bot):
+ info = m.text
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ choice = bot.reply_to(m, "Are you sure you want to delete? Y/N")
+ bot.register_next_step_handler(choice, delete_selected_data, bot, info)
+
+
+
+def delete_selected_data(message, bot, selected_data):
+ chat_id = message.chat.id
+ user_history = helper.getUserHistory(chat_id)
+
+ # Check if the user has selected any data
+ if not selected_data:
+ bot.send_message(chat_id, "No data selected for deletion.")
+ return
+
+ if str(message.text) == "Y" or str(message.text) == "y":
+ # Initialize a list to keep track of the deleted records
+ deleted_records = []
+
+ components = selected_data.split(',')
+ formatted_data = []
+
+ for component in components:
+ key, value = component.split('=')
+ value = value.strip()
+
+ # Check if the component is the "Amount" and remove the '$' sign
+ if key.strip() == "Amount" and value.startswith("$"):
+ value = value[1:]
+
+ formatted_data.append(value)
+
+ formatted_string = ','.join(formatted_data)
+
+ # Compare each item in user_history with selected_data
+ for expense_data in user_history:
+ if formatted_string in expense_data:
+ user_history.remove(expense_data)
+ deleted_records.append(expense_data)
+
+ # Update the user's history
+ user_list = helper.read_json()
+ user_list[str(chat_id)]["data"] = user_history
+ helper.write_json(user_list)
+
+ # Provide feedback to the user about the deleted records
+ if deleted_records:
+ bot.send_message(chat_id, "The following record has been deleted:")
+ # Create a tabular representation of the data
+ tabular_data = "```"
+ tabular_data += "+-------------------+-------------------+-------------+\n"
+ tabular_data += "| DATE | CATEGORY | AMOUNT |\n"
+ tabular_data += "+-------------------+-------------------+-------------+\n"
+
+ for line in deleted_records:
+ rec = line.split(",") # Assuming data is comma-separated
+ if len(rec) == 3:
+ tabular_data += "| {:<15} | {:<17} | {:<11} |\n".format(rec[0], rec[1], rec[2])
+
+ tabular_data += "+-------------------+-------------------+-------------+"
+ tabular_data += "```"
+
+ # Send the tabular data as a Markdown-formatted message
+ bot.send_message(chat_id, tabular_data, parse_mode="Markdown")
+
+ msg = bot.send_message(chat_id, "Do you want to see the updated expense history? Y/N")
+ bot.register_next_step_handler(msg, show_updated_expense_history, bot)
+
+ else:
+ bot.send_message(chat_id, "No matching records found for deletion.")
+ else:
+ bot.send_message(chat_id, "No data deleted.")
+
+def show_updated_expense_history(message, bot):
+ if str(message.text) == "Y" or str(message.text) == "y":
+ history.run(message, bot)
+
+# function to delete a record
+def deleteHistory(chat_id):
+ """
+ deleteHistory(chat_id): It takes 1 argument for processing - chat_id which is the
+ chat_id of the user whose data is to deleted from the user list. It removes this entry from the user list.
+ """
+ global user_list
+ if str(chat_id) in user_list:
+ del user_list[str(chat_id)]
+ return user_list
diff --git a/code/delete_user.py b/code/delete_user.py
new file mode 100644
index 000000000..d3db0ef6a
--- /dev/null
+++ b/code/delete_user.py
@@ -0,0 +1,50 @@
+import helper
+from telebot import types
+
+# Initialize a dictionary to store registered users
+registered_users = {}
+
+def delete_user(message, bot, user_list):
+ chat_id = message.chat.id
+ user_dict = user_list.get(str(chat_id), {})
+
+ if not user_dict or "users" not in user_dict:
+ bot.send_message(chat_id, "No users are registered for deletion.")
+ return
+
+ # Get the list of all users from user_list
+ all_users = user_dict.get("users", [])
+
+ # Create a custom keyboard to let the user choose which user to delete
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
+ for user in all_users:
+ markup.add(user)
+
+ msg = bot.send_message(chat_id, "Select the user you want to delete:", reply_markup=markup)
+ bot.register_next_step_handler(msg, confirm_delete, bot, user_list)
+
+def confirm_delete(message, bot, user_list):
+ chat_id = message.chat.id
+ user_name = message.text
+
+ user_dict = user_list.get(str(chat_id), {})
+
+ if "users" in user_dict and user_name in user_dict["users"]:
+ user_dict["users"].remove(user_name)
+ user_dict["owed"].pop(user_name, None)
+ user_dict["owing"].pop(user_name, None)
+
+ helper.write_json(user_list)
+
+ bot.send_message(chat_id, f"{user_name} has been deleted successfully.")
+
+ if not user_dict["users"]:
+ bot.send_message(chat_id, "No users are registered after deletion.")
+ else:
+ bot.send_message(chat_id, "Updated list of registered users:\n" + '\n '.join(user_dict["users"]))
+ else:
+ bot.send_message(chat_id, f"{user_name} is not registered.")
+
+ # Remove the custom keyboard
+ # markup = types.ReplyKeyboardRemove(selective=False)
+ # bot.send_message(chat_id, "Keyboard hidden. You can now use other commands.", reply_markup=markup)
diff --git a/code/display.py b/code/display.py
index 0d7715980..e30241835 100644
--- a/code/display.py
+++ b/code/display.py
@@ -14,7 +14,7 @@ def run(message, bot):
It takes 2 arguments for processing - message which is the message from the user, and bot
which is the telegram bot object from the main code.py function.
"""
- helper.read_json()
+ user_list=helper.read_json()
chat_id = message.chat.id
history = helper.getUserHistory(chat_id)
if history is None:
@@ -23,17 +23,56 @@ def run(message, bot):
)
else:
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- markup.row_width = 2
- for mode in helper.getSpendDisplayOptions():
- markup.add(mode)
- # markup.add('Day', 'Month')
- msg = bot.reply_to(
- message,
- "Please select a category to see the total expense",
- reply_markup=markup,
- )
- bot.register_next_step_handler(msg, display_total, bot)
+ markup.add("Display all expenses")
+ markup.add("Display owings")
+ m = bot.send_message(chat_id, "Select what to display",reply_markup=markup)
+ bot.register_next_step_handler(m, display_choice, bot,user_list,chat_id)
+
+
+def display_choice(message,bot,user_list,chat_id):
+ chat_id = message.chat.id
+ choice = message.text
+ if choice == 'Display all expenses':
+ display_expenses(message,bot)
+ elif choice =='Display owings':
+ display_owings(message,bot,user_list,chat_id)
+ else:
+ m = bot.send_message(chat_id, "Select correct choice")
+ bot.register_next_step_handler(m, run, bot)
+
+def display_owings(message,bot,user_list,chat_id):
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = len(user_list[str(chat_id)]["users"])
+ for c in user_list[str(chat_id)]["users"]:
+ markup.add(c)
+ m = bot.send_message(chat_id, "Select user who's owings you want to display",reply_markup=markup)
+ bot.register_next_step_handler(m, select_user, bot,user_list,chat_id)
+
+def select_user(message,bot,user_list,chat_id):
+ chat_id = message.chat.id
+ user = message.text
+ owing_dictionary = helper.calculate_owing(user_list,chat_id)
+ final_string = ''
+ for owed in owing_dictionary[user]["owes"]:
+ final_string+=str("\n "+owed)
+ for owing in owing_dictionary[user]["owing"]:
+ final_string+=str("\n "+owing)
+ if final_string == '':
+ final_string = str(user)+' owes or is owed nothing'
+ m = bot.send_message(chat_id, final_string)
+def display_expenses(message, bot):
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ for mode in helper.getSpendDisplayOptions():
+ markup.add(mode)
+ # markup.add('Day', 'Month')
+ msg = bot.reply_to(
+ message,
+ "Please select a category to see the total expense",
+ reply_markup=markup,
+ )
+ bot.register_next_step_handler(msg, display_total, bot)
def display_total(message, bot):
"""
@@ -77,21 +116,38 @@ def display_total(message, bot):
value for index, value in enumerate(history) if str(query) in value
]
total_text = calculate_spendings(queryResult)
+ print("###########",total_text)
monthly_budget = helper.getCategoryBudget(chat_id)
- print("Print Total Spending", total_text)
- print("Print monthly budget", monthly_budget)
+ if monthly_budget == None:
+ message = "Looks like you have not entered any category-wise budget yet. Please enter your budget and then try to display the expenses."
+ bot.send_message(chat_id, message)
- spending_text = ""
- if len(total_text) == 0:
- spending_text = "You have no spendings for {}!".format(DayWeekMonth)
- bot.send_message(chat_id, spending_text)
+ display_text = ""
+ commands = helper.getCommands()
+ for (
+ c
+ ) in (
+ commands
+ ): # generate help text out of the commands dictionary defined at the top
+ display_text += "/" + c + ": "
+ display_text += commands[c] + "\n"
+ bot.send_message(chat_id, "Please select a menu option from below:")
+ bot.send_message(chat_id, display_text)
else:
- spending_text = "Here are your total spendings {}:\nCATEGORIES,AMOUNT \n----------------------\n{}".format(
- DayWeekMonth.lower(), total_text
- )
- graphing.visualize(total_text, monthly_budget)
- bot.send_photo(chat_id, photo=open("expenditure.png", "rb"))
- # os.remove('expenditure.png')
+ print("Print Total Spending", total_text)
+ print("Print monthly budget", monthly_budget)
+
+ spending_text = ""
+ if len(total_text) == 0:
+ spending_text = "You have no spendings for {}!".format(DayWeekMonth)
+ bot.send_message(chat_id, spending_text)
+ else:
+ spending_text = "Here are your total spendings {}:\nCATEGORIES,AMOUNT \n----------------------\n{}".format(
+ DayWeekMonth.lower(), total_text
+ )
+ graphing.visualize(total_text, monthly_budget)
+ bot.send_photo(chat_id, photo=open("expenditure.png", "rb"))
+ # os.remove('expenditure.png')
except Exception as e:
logging.exception(str(e))
bot.reply_to(message, str(e))
@@ -104,7 +160,7 @@ def calculate_spendings(queryResult):
It parses the query result and turns it into a form suitable for display on the UI by the user.
"""
total_dict = {}
-
+ print("!!!!!!",queryResult)
for row in queryResult:
# date,cat,money
s = row.split(",")
diff --git a/code/graphing.py b/code/graphing.py
index 8e08ce69b..cb4d86ac1 100644
--- a/code/graphing.py
+++ b/code/graphing.py
@@ -39,8 +39,6 @@ def visualize(total_text, monthly_budget):
"""
n1 = len(monthly_budget)
r1 = np.arange(n1)
- print(n1)
- print(r1)
width = 0.45
total_text_split = [line for line in total_text.split("\n") if line.strip() != ""]
monthly_budget_str = ""
@@ -49,11 +47,6 @@ def visualize(total_text, monthly_budget):
monthly_budget_split = [
line for line in monthly_budget_str.split("\n") if line.strip() != ""
]
- categ_val = {}
- for i in total_text_split:
- a = i.split(" ")
- a[1] = a[1].replace("$", "")
- categ_val[a[0]] = float(a[1])
monthly_budget_categ_val = {}
for j in monthly_budget_split:
@@ -61,11 +54,16 @@ def visualize(total_text, monthly_budget):
x[1] = x[1].replace("$", "")
monthly_budget_categ_val[x[0]] = float(x[1])
+ categ_val = {key: 0 for key in monthly_budget_categ_val}
+ for i in total_text_split:
+ a = i.split(" ")
+ a[1] = a[1].replace("$", "")
+ categ_val[a[0]] = float(a[1])
+
x = list(categ_val.keys())
y = list(categ_val.values())
n2 = len(x)
r2 = np.arange(n2)
-
plt.bar(r2, categ_val.values(), width=width, label="your spendings")
plt.bar(
r1 + width, monthly_budget_categ_val.values(), width=width, label="your budget"
diff --git a/code/helper.py b/code/helper.py
index 70c8c48d4..1888f2e4b 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -4,7 +4,6 @@
from datetime import datetime
from notify import notify
-
spend_categories = [
"Food",
"Groceries",
@@ -18,25 +17,42 @@
spend_estimate_option = ["Next day", "Next month"]
update_options = {"continue": "Continue", "exit": "Exit"}
-budget_options = {"update": "Add/Update", "view": "View", "delete": "Delete"}
+budget_options = {"add":"Add","update": "Update", "view": "View", "delete": "Delete"}
budget_types = {"overall": "Overall Budget", "category": "Category-Wise Budget"}
-data_format = {"data": [], "budget": {"overall": None, "category": None}}
+data_format = {"users":[],"owed":{},"owing":{},"data": [],"csv_data":[],
+ "budget": {"overall": '0', "category": {"Food": '0',
+ "Groceries": '0',
+ "Utilities": '0',
+ "Transport": '0',
+ "Shopping": '0',
+ "Miscellaneous": '0'}
+ }
+}
# set of implemented commands and their description
commands = {
"help": "Display the list of commands.",
"pdf": "Save history as PDF.",
+ "csv": "Save history as a cv file.",
+ "add_user": "Add users to expense tracker",
+ "delete_user":"Delete user from the registered users",
"add": "This option is for adding your expenses \
\n 1. It will give you the list of categories to choose from. \
\n 2. You will be prompted to enter the amount corresponding to your spending \
\n 3.The message will be prompted to notify the addition of your expense with the amount,date, time and category ",
+ "add_category": "This option is for adding new category \
+ \n 1. You will be prompted to enter a new category \
+ \n 2.The message will be prompted to notify the addition of your category ",
+
"display": "This option gives user a graphical representation(bar graph) of their expenditures \
\n You will get an option to choose from day or month for better analysis of the expenses.",
"estimate": "This option gives you the estimate of expenditure for the next day/month. It calcuates based on your recorded spendings",
"history": "This option is to give you the detailed summary of your expenditure with Date, time ,category and amount. A quick lookup into your spendings",
"delete": "This option is to Clear/Erase all your records",
+ "delete_expense": "This option is to Clear/Erase individual record from expense history records.",
+ "send_mail": "This option is to send mail of calculate owings",
"edit": "This option helps you to go back and correct/update the missing details \
\n 1. It will give you the list of your expenses you wish to edit \
\n 2. It will let you change the specific field based on your requirements like amount/date/category",
@@ -109,7 +125,6 @@ def getUserHistory(chat_id):
return data["data"]
return None
-
def getUserData(chat_id):
user_list = read_json()
if user_list is None:
@@ -124,22 +139,32 @@ def throw_exception(e, message, bot, logging):
bot.reply_to(message, "Oh no! " + str(e))
-def createNewUserRecord():
- return data_format
+def createNewUserRecord(message):
+ user_lst = data_format
+ if len(user_lst["users"]) == 0:
+ user_lst["users"].insert(0,message.from_user.first_name)
+ user_lst["owed"][message.from_user.first_name] = 0
+ user_lst["owing"][message.from_user.first_name] = {}
+ return user_lst
def getOverallBudget(chatId):
data = getUserData(chatId)
if data is None:
return None
- return data["budget"]["overall"]
+ if 'budget' in data.keys():
+ return data["budget"]["overall"]
+ return None
def getCategoryBudget(chatId):
data = getUserData(chatId)
if data is None:
return None
- return data["budget"]["category"]
+ if 'budget' in data.keys():
+ return data["budget"]["category"]
+ return None
+
def getCategoryBudgetByCategory(chatId, cat):
@@ -209,6 +234,23 @@ def calculate_total_spendings(queryResult):
total = total + float(s[2])
return total
+def calculate_owing(user_list,chat_id):
+ owing_dict = {}
+ users = user_list[str(chat_id)]["users"]
+ for user in users:
+ owing_dict[user] = {"owes" : [], "owing":[]}
+ for k,v in user_list[str(chat_id)]["owing"][user].items():
+ if k in owing_dict.keys():
+ owing_dict[k]["owing"].append(str(user)+' owes '+str(k)+" an amout of "+"{:.2f}".format(v))
+ owing_dict[user]["owes"].append(str(k)+' is owing from '+str(user)+" an amout of "+"{:.2f}".format(v))
+
+ else:
+ owing_dict[k] ={"owes" :[str(k)+' is owing from '+str(user)+" an amout of "+"{:.2f}".format(v)],"owing" :[str(user)+' owes '+str(k)+" an amout of "+"{:.2f}".format(v)]}
+
+ return owing_dict
+
+
+
def display_remaining_category_budget(message, bot, cat):
chat_id = message.chat.id
diff --git a/code/history.py b/code/history.py
index c52978512..df99dcad9 100644
--- a/code/history.py
+++ b/code/history.py
@@ -1,6 +1,7 @@
import helper
import logging
-
+import csv
+from io import StringIO
# === Documentation of history.py ===
@@ -12,20 +13,62 @@ def run(message, bot):
historical data and based on whether there is data available, it either prints an error message or
displays the user's historical data.
"""
+
+ # try:
+ # helper.read_json()
+ # chat_id = message.chat.id
+ # user_history = helper.getUserHistory(chat_id)
+
+ # if user_history is None:
+ # raise Exception("Sorry! No spending records found!")
+
+ # if len(user_history) == 0:
+ # bot.send_message(chat_id, "Sorry! No spending records found!")
+ # else:
+ # # Create a CSV representation of the data
+ # csv_data = "DATE, CATEGORY, AMOUNT\n"
+
+ # for line in user_history:
+ # rec = line.split(",") # Assuming data is comma-separated
+ # if len(rec) == 3:
+ # csv_data += f"{rec[0]}, {rec[1]}, {rec[2]}\n"
+
+ # # Send the CSV data as a text message
+ # bot.send_message(chat_id, csv_data)
+
+ # except Exception as e:
+ # logging.exception(str(e))
+ # bot.reply_to(message, "Oops! " + str(e))
+
try:
helper.read_json()
chat_id = message.chat.id
user_history = helper.getUserHistory(chat_id)
- spend_total_str = ""
+
if user_history is None:
raise Exception("Sorry! No spending records found!")
- spend_total_str = "Here is your spending history : \nDATE, CATEGORY, AMOUNT\n----------------------\n"
+
if len(user_history) == 0:
- spend_total_str = "Sorry! No spending records found!"
+ bot.send_message(chat_id, "Sorry! No spending records found!")
else:
- for rec in user_history:
- spend_total_str += str(rec) + "\n"
- bot.send_message(chat_id, spend_total_str)
+ # Create a tabular representation of the data
+ tabular_data = "```"
+ tabular_data += "+-------------------+-------------------+-------------+\n"
+ tabular_data += "| DATE | CATEGORY | AMOUNT |\n"
+ tabular_data += "+-------------------+-------------------+-------------+\n"
+
+ for line in user_history:
+ rec = line.split(",") # Assuming data is comma-separated
+ if len(rec) == 3:
+ tabular_data += "| {:<15} | {:<17} | {:<11} |\n".format(rec[0], rec[1], rec[2])
+
+ tabular_data += "+-------------------+-------------------+-------------+"
+ tabular_data += "```"
+
+ # Send the tabular data as a Markdown-formatted message
+ bot.send_message(chat_id, tabular_data, parse_mode="Markdown")
+
except Exception as e:
logging.exception(str(e))
- bot.reply_to(message, "Oops!" + str(e))
+ bot.reply_to(message, "Oops! " + str(e))
+
diff --git a/code/pdf.py b/code/pdf.py
index 6996ad32f..faf67500f 100644
--- a/code/pdf.py
+++ b/code/pdf.py
@@ -1,6 +1,20 @@
import helper
import logging
from matplotlib import pyplot as plt
+from telebot import types
+
+#Issue 15 - Added tabulate and fpdf libraries
+from tabulate import tabulate
+from fpdf import FPDF
+
+#Issue 23 - Added Tabula library
+#import tabula
+
+#from reportlab.lib import colors
+#from reportlab.lib.pagesizes import letter
+#from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
+
+#Issue - 33
# === Documentation of pdf.py ===
@@ -12,42 +26,164 @@ def run(message, bot):
try:
helper.read_json()
chat_id = message.chat.id
+
+ user_list = helper.read_json()
+ #print('User-history--> ',user_history)
+ print('User_list', user_list)
+
+ #Issue 23 - add two pdf types - start
+
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ #markup.row_width = 2
+ markup.add("PDF for Total Expenses - Category wise", "PDF showing who owes whom how much")
+ msg = bot.send_message(chat_id, "Which kind of PDF do you want to generate?", reply_markup=markup)
+
user_history = helper.getUserHistory(chat_id)
- message = "Alright. I just created a pdf of your expense history!"
- bot.send_message(chat_id, message)
- fig = plt.figure()
- ax = fig.add_subplot(1, 1, 1)
- top = 0.8
- if len(user_history) == 0:
- plt.text(
- 0.1,
- top,
- "No record found!",
- horizontalalignment="left",
- verticalalignment="center",
- transform=ax.transAxes,
- fontsize=20,
- )
- for rec in user_history:
- date, category, amount = rec.split(",")
- date, time = date.split(" ")
- print(date, category, amount)
- rec_str = f"{amount}$ {category} expense on {date} at {time}"
- plt.text(
- 0,
- top,
- rec_str,
- horizontalalignment="left",
- verticalalignment="center",
- transform=ax.transAxes,
- fontsize=14,
- bbox=dict(facecolor="red", alpha=0.3),
- )
- top -= 0.15
- plt.axis("off")
- plt.savefig("expense_history.pdf")
- plt.close()
- bot.send_document(chat_id, open("expense_history.pdf", "rb"))
+
+ bot.register_next_step_handler(msg, pdfGeneration, bot,user_list, user_history)
+ #Issue 23 - add two pdf types - end
+
except Exception as e:
logging.exception(str(e))
bot.reply_to(message, "Oops!" + str(e))
+
+def pdfGeneration(message, bot, user_list, user_history):
+ chat_id = message.chat.id
+ choice = message.text
+
+ if choice == 'PDF for Total Expenses - Category wise':
+ #Issue 3 - added the if condition - start
+ if user_history != None:
+ #Issue 3 - added the if condition - end
+ message = "Alright. I just created a pdf of your expense history!"
+ bot.send_message(chat_id, message)
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1)
+ top = 0.8
+ if len(user_history) == 0:
+ plt.text(
+ 0.1,
+ top,
+ "No record found!",
+ horizontalalignment="left",
+ verticalalignment="center",
+ transform=ax.transAxes,
+ fontsize=20,
+ )
+
+ #issue 15 - modified the format of pdf document - start
+ table_data = [entry.split(',') for entry in user_history]
+
+ # Create a PDF document
+ pdf = FPDF()
+ pdf.add_page()
+
+ # Set font
+ pdf.set_font("helvetica", size=12)
+
+ # Define the table columns
+ columns = ["Date & Time", "Category", "Amount"]
+
+ # Create a table and set its properties
+ pdf.set_fill_color(135, 206, 235) # Light blue
+ pdf.set_font(family="helvetica",style="B")
+ pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True)
+ pdf.set_fill_color(255, 255, 255) # White
+ pdf.ln()
+ pdf.ln()
+ # Add table headers
+ pdf.set_font("helvetica", size=12, style="B")
+ for col in columns:
+ pdf.cell(64, 10, col, border=1, align="C", fill=True)
+ pdf.ln()
+
+ # Add table data
+ pdf.set_font("helvetica", size=10)
+ for row in table_data:
+ for item in row:
+ pdf.cell(64, 10, item, border=1, align="C", fill=True)
+ pdf.ln()
+
+ # Save the PDF
+ pdf.output("expense_report.pdf")
+ bot.send_document(chat_id, open("expense_report.pdf", "rb"))
+ print("PDF generated successfully.")
+ #issue 15 - modified the format of pdf document - start
+
+
+ #Issue 3 - added the else condition - start
+ else:
+ message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf."
+ bot.send_message(chat_id, message)
+
+ display_text = ""
+ commands = helper.getCommands()
+ for (
+ c
+ ) in (
+ commands
+ ): # generate help text out of the commands dictionary defined at the top
+ display_text += "/" + c + ": "
+ display_text += commands[c] + "\n"
+ bot.send_message(chat_id, "Please select a menu option from below:")
+ bot.send_message(chat_id, display_text)
+ #Issue 3 - added the else condition - end
+ #Issue 23 - added code for generating pdf showing who owes whom how much
+ elif choice == 'PDF showing who owes whom how much':
+ message = "Alright. I just created a pdf of your expense history!"
+ bot.send_message(chat_id, message)
+
+ if user_history != None:
+ pdf = FPDF()
+ pdf.add_page()
+
+ pdf.set_font("Arial", size=12)
+ pdf.set_fill_color(135, 206, 235) # Light blue
+ pdf.set_font(family="Arial",style="B")
+ pdf.cell(0, 10, "Expense Report", ln=1, align="C", fill=True)
+ pdf.set_fill_color(255, 255, 255) # White
+ pdf.ln()
+ pdf.ln()
+
+ pdf.set_font("helvetica", size=12, style="B")
+ pdf.cell(50, 10, "User", border=1, align="C", fill=True)
+ pdf.cell(50, 10, "Gets back", border=1, align="C", fill=True)
+ pdf.cell(50, 10, "Gives to", border=1, align="C", fill=True)
+ pdf.cell(40, 10, "Gives Amount", border=1, align="C", fill=True)
+ #pdf.cell(40, 10, "Data", border=1)
+ pdf.ln()
+
+ pdf.set_font("helvetica", size=12)
+ for user, details in user_list.items():
+ #print('User! ', user)
+ #print('details! ', details)
+ for i, user_name in enumerate(details["users"]):
+ #amounts = ''
+ pdf.cell(50, 10, user_name, border=1, align="C", fill=True)
+ pdf.cell(50, 10, str(round(details["owed"][user_name], 2)), border=1, align="C", fill=True)
+ pdf.cell(50, 10, ', '.join(details["owing"][user_name].keys()), border=1, align="C", fill=True)
+ if details["owing"][user_name].values() != []:
+ pdf.cell(40, 10, ', '.join([str(round(x,2)) for x in details["owing"][user_name].values()]), border=1, align="C", fill=True)
+ else:
+ pdf.cell(40, 10, "None", border=1, align="C", fill=True)
+ pdf.ln()
+
+ pdf.output("OwingTable.pdf")
+ bot.send_document(chat_id, open("OwingTable.pdf", "rb"))
+ print("PDF table created successfully.")
+
+ else:
+ message = "Looks like you have not entered any data yet. Please enter some data and then try creating a pdf."
+ bot.send_message(chat_id, message)
+
+ display_text = ""
+ commands = helper.getCommands()
+ for (
+ c
+ ) in (
+ commands
+ ): # generate help text out of the commands dictionary defined at the top
+ display_text += "/" + c + ": "
+ display_text += commands[c] + "\n"
+ bot.send_message(chat_id, "Please select a menu option from below:")
+ bot.send_message(chat_id, display_text)
\ No newline at end of file
diff --git a/code/send_mail.py b/code/send_mail.py
new file mode 100644
index 000000000..94d6980e0
--- /dev/null
+++ b/code/send_mail.py
@@ -0,0 +1,102 @@
+import helper
+import smtplib
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from fpdf import FPDF
+
+user_emails = {}
+
+# === Documentation of add.py ===
+def run(message, bot):
+ helper.read_json()
+ chat_id = message.chat.id
+ message1 = bot.send_message(chat_id, "Please enter the email address")
+ bot.register_next_step_handler(message1, add_emails, bot)
+
+def add_emails(message, bot):
+ chat_id = message.chat.id
+ email = message.text
+ # Assuming you want to store the email address in the user_emails dictionary
+ user_emails[chat_id] = email
+
+ # You can also validate the email address if needed
+ if not is_valid_email(email):
+ bot.send_message(chat_id, "Invalid email address. Please enter a valid email.")
+ return
+
+ # Notify the user that their email address has been recorded
+ choice = bot.send_message(chat_id, f"Thank you for providing your email. Do you want to send email to {email}? Y/N")
+ bot.register_next_step_handler(choice, send_email, bot)
+
+# Example of a basic email validation function (you can expand this)
+def is_valid_email(email):
+ import re
+ email_pattern = r'^\S+@\S+\.\S+$'
+ return re.match(email_pattern, email) is not None
+
+def send_email(choice, bot):
+ if str(choice.text) == "Y" or str(choice.text) == "y":
+ # Set up the Gmail API
+ smtp_server = 'smtp.gmail.com'
+ smtp_port = 587 # Port for TLS
+ smtp_username = 'csc510group32@gmail.com'
+ smtp_password = 'hqrx opxo lviu mubb'
+
+ # Create an SMTP connection
+ server = smtplib.SMTP(smtp_server, smtp_port)
+ server.starttls()
+ server.login(smtp_username, smtp_password)
+
+
+ # Compose and send emails to all users
+ for chat_id, email in user_emails.items():
+ # Create a MIME message with a subject
+ subject = "Calculated Owings"
+ message_body = format_text_data(helper.read_json())
+ message = MIMEMultipart()
+ message['From'] = smtp_username
+ message['To'] = email
+ message['Subject'] = subject
+ message.attach(MIMEText(message_body, 'plain'))
+
+ # Send the email
+ server.sendmail(smtp_username, email, message.as_string())
+
+ # Close the SMTP connection
+ server.quit()
+
+
+
+def format_text_data(user_list):
+ text_data = "```\n"
+
+ for user, details in user_list.items():
+ for user_name in details["users"]:
+ gets_back_amount = round(details['owed'][user_name], 2)
+ text_data += f"{user_name} gets back {gets_back_amount} dollars.\n"
+
+ gives_to_list = list(details["owing"][user_name].keys())
+ gives_amount_list = list(details["owing"][user_name].values())
+
+ if not gives_to_list:
+ text_data += f"{user_name} gives to no one.\n"
+ else:
+ for i in range(len(gives_to_list)):
+ gives_to_entry = gives_to_list[i]
+ gives_amount_entry = round(gives_amount_list[i], 2)
+ text_data += f"{user_name} gives {gives_amount_entry} dollars to {gives_to_entry}.\n"
+
+ text_data += "```"
+ return text_data
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/add.md b/docs/add.md
index 118589551..63c8f3770 100644
--- a/docs/add.md
+++ b/docs/add.md
@@ -1,4 +1,5 @@
# About MyDollarBot's /add Feature
+#TO-DO
This feature enables the user to add a new expense to their expense tracker.
Currently we have the following expense categories set by default:
diff --git a/docs/add_category.md b/docs/add_category.md
new file mode 100644
index 000000000..01185bc52
--- /dev/null
+++ b/docs/add_category.md
@@ -0,0 +1,40 @@
+# About MyDollarBot's /add_category Feature
+#TO-DO
+This feature enables the user to add a new category.
+Currently we have the following expense categories set by default:
+
+- Food
+- Groceries
+- Utilities
+- Transport
+- Shopping
+- Miscellaneous
+
+
+# Location of Code for this Feature
+The code that implements this feature can be found [here](https://github.com/sak007/MyDollarBot-BOTGo/blob/main/code/add_category.py)
+
+# Code Description
+## Functions
+
+1. run(message, bot):
+This is the main function used to implement the add_category feature. It asks the user to add a new category, after which control is given to post_append_spend(message, bot) for further proccessing. It takes 2 arguments for processing - **message** which is the message from the user, and **bot** which is the telegram bot object from the main code.py function.
+
+2. post_append_spend(message, bot):
+ It takes 2 arguments for processing - **message** which is the message from the user, and **bot** which is the telegram bot object from the run(message, bot): function in the add_category.py file. It requests the user to enter the a new category. It also handles the case of keeping the category names unique.
+
+
+# How to run this feature?
+Once the project is running(please follow the instructions given in the main README.md for this), please type /add_category into the telegram bot.
+
+Sho, [13-10-2023 15:04]
+/add_category
+
+testbot_SSAR, [13-10-2023 15:13]
+Please enter your category
+
+Sho, [13-10-2023 15:13]
+vehicle
+
+testbot_SSAR, [13-10-2023 15:13]
+The following category has been added: vehicle
\ No newline at end of file
diff --git a/docs/dollar_bot/**/*.html b/docs/dollar_bot/**/*.html
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docs/dollar_bot/code/*.html b/docs/dollar_bot/code/*.html
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docs/history.md b/docs/history.md
index c259a341a..739d94824 100644
--- a/docs/history.md
+++ b/docs/history.md
@@ -13,23 +13,11 @@ This is the main function used to implement the delete feature. It takes 2 argum
# How to run this feature?
Once the project is running(please follow the instructions given in the main README.md for this), please type /add into the telegram bot.
-Below you can see an example in text format:
-
-Sri Athithya Kruth, [20.10.21 20:33]
-/display
-
-Sri Athithya Kruth, [20.10.21 20:33]
-Day
-
-mydollarbot20102021, [20.10.21 20:33]
-Hold on! Calculating...
-
-Sri Athithya Kruth, [20.10.21 20:53]
+AGM, [13-10-2023 05:48 PM]
/history
-mydollarbot20102021, [20.10.21 20:53]
-Here is your spending history :
-DATE, CATEGORY, AMOUNT
-----------------------
-20-Oct-2021 20:33,Transport,1022.0
-20-Oct-2021 20:33,Groceries,12.0
\ No newline at end of file
+agmalpur, [13-10-2023 05:48 PM]
+| DATE | CATEGORY | AMOUNT |
++-------------------+-------------------+-------------+
+| 13-Oct-2023 16:55 | Transport | 8.0 |
++-------------------+-------------------+-------------+
diff --git a/python_docs/code.html b/python_docs/code.html
index 187903a33..b02ce5b58 100644
--- a/python_docs/code.html
+++ b/python_docs/code.html
@@ -158,7 +158,10 @@ Documen
faq_message = '"What does this bot do?"\n' + \
'>> DollarBot lets you manage your expenses so you can always stay on top of them! \n\n' + \
- '"How can I add an epxense?" \n' + \
+ '"How can I add a category?" \n' + \
+ '>> Type /add_category, then add a category for the expense. \n\n' + \
+
+ '"How can I add an expense?" \n' + \
'>> Type /add, then select a category to type the expense. \n\n' + \
'"Can I see history of my expenses?" \n' + \
'>> Yes! Use /display to get a graphical display, or /history to view detailed summary.\n\n' + \
diff --git a/requirements.txt b/requirements.txt
index 082b6f120..ba0924101 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,4 +5,6 @@ flake8
matplotlib
coverage
pytest-mock
-python-telegram-bot-calendar
\ No newline at end of file
+python-telegram-bot-calendar
+tabulate
+FPDF
\ No newline at end of file
diff --git a/user.properties b/user.properties
index 6923b0754..fe5a204c9 100644
--- a/user.properties
+++ b/user.properties
@@ -1,2 +1,3 @@
api_token=2124576840:AAF4GNT5QuNmfnOFjfCwU4JPu2xOqkOVgJA
+