From fb516619b8a9732bca6b7a5614d829f4b8b7f11e Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sat, 7 Oct 2023 20:03:42 -0400
Subject: [PATCH 001/135] Removed empty doco files not compatible with windows
---
CONTRIBUTING.md | 0
code/.DS_Store | Bin 6148 -> 0 bytes
docs/dollar_bot/**/*.html | 0
docs/dollar_bot/code/*.html | 0
run.sh | 0
run_forever.sh | 0
6 files changed, 0 insertions(+), 0 deletions(-)
mode change 100755 => 100644 CONTRIBUTING.md
delete mode 100644 code/.DS_Store
delete mode 100644 docs/dollar_bot/**/*.html
delete mode 100644 docs/dollar_bot/code/*.html
mode change 100755 => 100644 run.sh
mode change 100755 => 100644 run_forever.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
old mode 100755
new mode 100644
diff --git a/code/.DS_Store b/code/.DS_Store
deleted file mode 100644
index 87aa52603c59f86501204f5a6065fd1b0c75d99e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 6148
zcmeHKyG{c!5S)cbL`ridrN01)Kd_=uq~-@u61qr7LqT^H-^HK7>_dofL875Sv(kF(
z^^Pr1aeE8E*3XAqU;wbBJL1)cx%s~P%q}WoL^{v7!4qD#$8oH!68}z
z;)3BY&ZCzgHV+Vc;grY-&5}w?s#S|&NoTxOUN4*ylMaiUc~0GI)uCA2&UlM-SWna_
z1*E{K0++cQy#C+Of9U^DNm@w(De$ipu*K$nv*wekw$2{swYJe8=$`XNcjG)L9HJZ(
jqa1VL<@h?1GOziZ`@L{V3_9aMC+cUwb&*MdzgFM_h?peV
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/run.sh b/run.sh
old mode 100755
new mode 100644
diff --git a/run_forever.sh b/run_forever.sh
old mode 100755
new mode 100644
From 23d03e1d2ed71d0b159961695e977f593002c7e7 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 05:58:15 +0530
Subject: [PATCH 002/135] Update and rename custom.md to bug-template.md
---
.github/ISSUE_TEMPLATE/{custom.md => bug-template.md} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename .github/ISSUE_TEMPLATE/{custom.md => bug-template.md} (83%)
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/bug-template.md
similarity index 83%
rename from .github/ISSUE_TEMPLATE/custom.md
rename to .github/ISSUE_TEMPLATE/bug-template.md
index 956abfc2d..8ebe75239 100644
--- a/.github/ISSUE_TEMPLATE/custom.md
+++ b/.github/ISSUE_TEMPLATE/bug-template.md
@@ -1,5 +1,5 @@
---
-name: Custom issue template
+name: Bug Issue Template
about: To make contributors familiar with the structure for reporting or opening issues
title: 'to-do '
labels: ''
From 1a9fc03ae8f94b9c8c64b3ee64d9957707c5cce5 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 05:58:56 +0530
Subject: [PATCH 003/135] Update and rename issue-task-template.md to
docu-template.md
---
.../{issue-task-template.md => docu-template.md} | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
rename .github/ISSUE_TEMPLATE/{issue-task-template.md => docu-template.md} (59%)
diff --git a/.github/ISSUE_TEMPLATE/issue-task-template.md b/.github/ISSUE_TEMPLATE/docu-template.md
similarity index 59%
rename from .github/ISSUE_TEMPLATE/issue-task-template.md
rename to .github/ISSUE_TEMPLATE/docu-template.md
index a3c82bdf6..fac5e4971 100644
--- a/.github/ISSUE_TEMPLATE/issue-task-template.md
+++ b/.github/ISSUE_TEMPLATE/docu-template.md
@@ -1,6 +1,6 @@
---
-name: Issue Task Template
-about: To make contributors familiar with the structure for reporting or opening issues
+name: Docuemntation Template
+about: To make contributors familiar with the structure for reporting or opening issues related to documentation
title: 'to-do '
labels: ''
assignees: ''
From b5239a2adfcc0e59fe4105a22d29de3c108d789e Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:03:26 +0530
Subject: [PATCH 004/135] Update docu-template.md
---
.github/ISSUE_TEMPLATE/docu-template.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/docu-template.md b/.github/ISSUE_TEMPLATE/docu-template.md
index fac5e4971..9097bff76 100644
--- a/.github/ISSUE_TEMPLATE/docu-template.md
+++ b/.github/ISSUE_TEMPLATE/docu-template.md
@@ -1,7 +1,7 @@
---
-name: Docuemntation Template
-about: To make contributors familiar with the structure for reporting or opening issues related to documentation
-title: 'to-do '
+name: Documentation Template
+about: Exactly explain the issue and what you wish existed instead / what can be improved in the documentation.
+title: 'Describe the problem here concisely'
labels: ''
assignees: ''
From bfc15d18f9e8f28bc25a86474b5db26ab0fafdd3 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:06:57 +0530
Subject: [PATCH 005/135] Update bug-template.md
---
.github/ISSUE_TEMPLATE/bug-template.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug-template.md b/.github/ISSUE_TEMPLATE/bug-template.md
index 8ebe75239..0d9defcae 100644
--- a/.github/ISSUE_TEMPLATE/bug-template.md
+++ b/.github/ISSUE_TEMPLATE/bug-template.md
@@ -1,8 +1,8 @@
---
-name: Bug Issue Template
-about: To make contributors familiar with the structure for reporting or opening issues
-title: 'to-do '
-labels: ''
+name: Bug Issue Reports
+about: Create a bug report to help DollarBot improve!
+title: 'Bug in : describe the bug in a few words>'
+labels: 'bug'
assignees: ''
---
From 38f938d603897ac57169613f32870e08430ede48 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:08:17 +0530
Subject: [PATCH 006/135] Update docu-template.md
---
.github/ISSUE_TEMPLATE/docu-template.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/docu-template.md b/.github/ISSUE_TEMPLATE/docu-template.md
index 9097bff76..fcfdc7d7e 100644
--- a/.github/ISSUE_TEMPLATE/docu-template.md
+++ b/.github/ISSUE_TEMPLATE/docu-template.md
@@ -1,8 +1,8 @@
---
name: Documentation Template
about: Exactly explain the issue and what you wish existed instead / what can be improved in the documentation.
-title: 'Describe the problem here concisely'
-labels: ''
+title: 'Docu Issue in: < the section where documentation is lacking >'
+labels: 'documentation'
assignees: ''
---
From 7410c5a3f42c4b8f0d8b7c743f3abe909f0fcd9c Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:09:09 +0530
Subject: [PATCH 007/135] Update docu-template.md
---
.github/ISSUE_TEMPLATE/docu-template.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/docu-template.md b/.github/ISSUE_TEMPLATE/docu-template.md
index fcfdc7d7e..0eccddbcb 100644
--- a/.github/ISSUE_TEMPLATE/docu-template.md
+++ b/.github/ISSUE_TEMPLATE/docu-template.md
@@ -1,7 +1,7 @@
---
name: Documentation Template
about: Exactly explain the issue and what you wish existed instead / what can be improved in the documentation.
-title: 'Docu Issue in: < the section where documentation is lacking >'
+title: 'Docu Issue in: "the section where documentation is lacking"'
labels: 'documentation'
assignees: ''
From 7da45cec2d0dfe0a3e51f4fe3a9ca9ef91515a96 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:11:13 +0530
Subject: [PATCH 008/135] Create enhancement.md
---
.github/ISSUE_TEMPLATE/enhancement.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md
new file mode 100644
index 000000000..bb9057432
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement.md
@@ -0,0 +1,8 @@
+---
+name: Enhancement Suggestions
+about: Have cool feature enhancement ideas? Share here!
+title: 'Enhancement: XYZ in ABC feature'
+labels: 'enhancement'
+assignees: ''
+
+---
From 4522626bacba2a0532dc12f8f40621845f257e3f Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:11:50 +0530
Subject: [PATCH 009/135] Update docu-template.md
---
.github/ISSUE_TEMPLATE/docu-template.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/docu-template.md b/.github/ISSUE_TEMPLATE/docu-template.md
index 0eccddbcb..a1435eb87 100644
--- a/.github/ISSUE_TEMPLATE/docu-template.md
+++ b/.github/ISSUE_TEMPLATE/docu-template.md
@@ -1,5 +1,5 @@
---
-name: Documentation Template
+name: Documentation Improvement Ideas
about: Exactly explain the issue and what you wish existed instead / what can be improved in the documentation.
title: 'Docu Issue in: "the section where documentation is lacking"'
labels: 'documentation'
From bac7fb07070021716a56361fcdf3bfcca9347e7d Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 8 Oct 2023 06:13:32 +0530
Subject: [PATCH 010/135] Create other-issues.md
---
.github/ISSUE_TEMPLATE/other-issues.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/other-issues.md
diff --git a/.github/ISSUE_TEMPLATE/other-issues.md b/.github/ISSUE_TEMPLATE/other-issues.md
new file mode 100644
index 000000000..cd2128dc8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/other-issues.md
@@ -0,0 +1,8 @@
+---
+name: Other Issue Reports
+about: Don't see your issue above? Tell us here.
+title: 'Issue in : describe the issue in a few words>'
+labels: ''
+assignees: ''
+
+---
From 626cbebeae8eaa68c65cbc097baf2c3cf7e9940d Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sat, 7 Oct 2023 20:52:02 -0400
Subject: [PATCH 011/135] budget command bugfixes
---
code/budget_update.py | 24 ++++++++++++------------
code/budget_view.py | 14 ++++++++------
2 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/code/budget_update.py b/code/budget_update.py
index df8041f4a..4d1e867d9 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -13,18 +13,13 @@ def run(message, bot):
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)
+ 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):
@@ -174,10 +169,15 @@ def post_category_amount_input(message, bot, category):
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
+ if(user_list[str(chat_id)]["budget"]["overall"]):
+ user_list[str(chat_id)]["budget"]["overall"] = str(round(float(user_list[str(chat_id)]["budget"]["overall"]) + float(amount_value),2))
+ else:
+ user_list[str(chat_id)]["budget"]["overall"] = amount_value
helper.write_json(user_list)
message = bot.send_message(
chat_id, "Budget for " + category + " is now: $" + amount_value
)
+ budget_view.display_overall_budget(message, bot)
post_category_add(message, bot)
except Exception as e:
diff --git a/code/budget_view.py b/code/budget_view.py
index 077f8327b..b6958fcf7 100644
--- a/code/budget_view.py
+++ b/code/budget_view.py
@@ -19,9 +19,8 @@ def run(message, bot):
try:
print("here")
chat_id = message.chat.id
- if helper.isOverallBudgetAvailable(chat_id):
+ if helper.isOverallBudgetAvailable(chat_id) or helper.isCategoryBudgetAvailable(chat_id):
display_overall_budget(message, bot)
- elif helper.isCategoryBudgetAvailable(chat_id):
display_category_budget(message, bot)
else:
raise Exception(
@@ -52,7 +51,10 @@ def display_category_budget(message, bot):
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 helper.isCategoryBudgetAvailable(chat_id):
+ data = helper.getCategoryBudget(chat_id)
+ graphing.viewBudget(data)
+ bot.send_photo(chat_id, photo=open("budget.png", "rb"))
+ os.remove("budget.png")
+ else:
+ bot.send_message(chat_id, "You are yet to set your budget for different categories.")
From 17659905d695dc2df3809b27d9b6e6061b7b101c Mon Sep 17 00:00:00 2001
From: Aditi Killedar
Date: Sat, 7 Oct 2023 22:03:10 -0400
Subject: [PATCH 012/135] Fixed bug #13 in run.sh and split into setup.sh (#14
solved)
---
run.sh | 24 +++++-------------------
setup.sh | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+), 19 deletions(-)
create mode 100644 setup.sh
diff --git a/run.sh b/run.sh
index 3691de932..929e2bc0e 100644
--- a/run.sh
+++ b/run.sh
@@ -1,28 +1,14 @@
-pip3 install -r requirements.txt
-
api_token=$(grep "api_token" user.properties|cut -d'=' -f2)
+echo "Checking for API Token..."
if [ -z "$api_token" ]
then
- echo "Api token missing: Execute the following steps to generate Api token"
- echo
- echo "1. Download and install the Telegram desktop application for your system from the following site: https://desktop.telegram.org/"
- echo "2. Once you login to your Telegram account, search for \"BotFather\" in Telegram. Click on \"Start\" --> enter the following command:"
- echo "/newbot"
- echo "3. Follow the instructions on screen and choose a name for your bot. Post this, select a username for your bot that ends with \"bot\" (as per the instructions on your Telegram screen)"
- echo "4. BotFather will now confirm the creation of your bot and provide a TOKEN to access the HTTP API - copy this token."
- echo
- echo "Add Api token and continue?(y/n)"
- read option
- if [ $option == 'y' -o $option == 'Y' ]
- then
- echo "Enter the copied token: "
- read api_token
- echo "api_token="$api_token >> user.properties
- fi
+ echo "Oh no! Looks like you're missing an API token. No worries."
+ echo "Please run the 'setup.sh' file first."
fi
if [ -n "$api_token" ]
then
+ echo "Waking up DollarBot..."
python3 code/code.py
-fi
+fi
\ No newline at end of file
diff --git a/setup.sh b/setup.sh
new file mode 100644
index 000000000..ef84e5680
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,34 @@
+pip3 install -r requirements.txt
+
+api_token=$(grep "api_token" user.properties|cut -d'=' -f2)
+
+flag = "old"
+
+echo "Checking for API Token..."
+if [ -z "$api_token" ]
+then
+ echo "Welcome to DollarBot!"
+ echo "Follow the steps below to generate an API token to uniquely identify your personal DollarBot. Then, proceed to enter the generated token when prompted to run DollarBot."
+ echo
+ echo "1. Download and install the Telegram desktop application for your system from the following site: https://desktop.telegram.org/"
+ echo "2. Once you login to your Telegram account, search for \"BotFather\" in Telegram. Click on \"Start\" --> enter the following command:"
+ echo "/newbot"
+ echo "3. Follow the instructions on screen and choose a name for your bot. Post this, select a username for your bot that ends with \"bot\" (as per the instructions on your Telegram screen)"
+ echo "4. BotFather will now confirm the creation of your bot and provide a TOKEN to access the HTTP API - copy this token."
+ echo
+ echo "Do you want to add your API token now? (Y/n)"
+ read option
+ if [ $option == 'y' -o $option == 'Y' ]
+ then
+ flag = "new"
+ echo "Enter the copied token: "
+ read api_token
+ echo "api_token="$api_token >> user.properties
+ fi
+fi
+
+if [ -n "$api_token" ]
+then
+ echo "Thanks for choosing DollarBot! Starting DollarBot with new API token..."
+ python3 code/code.py
+fi
\ No newline at end of file
From 5325efcc68d1f44079c1fdfab80729bc1c5500a6 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sun, 8 Oct 2023 15:27:29 -0400
Subject: [PATCH 013/135] add new category while setting category-wise budget
---
.gitignore | 1 +
code/budget_update.py | 62 +++++++++++++++++++++++++++----------------
user.properties | 2 +-
3 files changed, 41 insertions(+), 24 deletions(-)
diff --git a/.gitignore b/.gitignore
index 507090ef0..7407d15c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,3 +133,4 @@ dmypy.json
# Generated files
expense_record.json
+user.properties
\ No newline at end of file
diff --git a/code/budget_update.py b/code/budget_update.py
index 4d1e867d9..a601d3cb5 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -106,6 +106,7 @@ def update_category_budget(message, bot):
markup.row_width = 2
for c in categories:
markup.add(c)
+ markup.add("Add new category")
msg = bot.reply_to(message, "Select Category", reply_markup=markup)
bot.register_next_step_handler(msg, post_category_selection, bot)
@@ -123,34 +124,49 @@ def post_category_selection(message, bot):
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),
- )
+ if selected_category == "Add new category":
+ message1 = bot.send_message(chat_id, "Please enter your category")
+ bot.register_next_step_handler(message1, add_new_category, bot)
else:
- message = bot.send_message(
- chat_id,
- "Enter monthly budget for " + selected_category + "\n(Enter numeric values only)",
+ 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
)
- bot.register_next_step_handler(
- message, post_category_amount_input, bot, selected_category
- )
except Exception as e:
helper.throw_exception(e, message, bot, logging)
+def add_new_category(message,bot):
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ new_category = message.text
+ helper.spend_categories.append(new_category)
+ 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)
+
+
def post_category_amount_input(message, bot, category):
"""
diff --git a/user.properties b/user.properties
index 6923b0754..5e0945f57 100644
--- a/user.properties
+++ b/user.properties
@@ -1,2 +1,2 @@
-api_token=2124576840:AAF4GNT5QuNmfnOFjfCwU4JPu2xOqkOVgJA
+api_token=6457044894:AAEGBeWPY6cdZhLuNe_2UYNuoFatM2Fnsgo
\ No newline at end of file
From 7d07ce4564cf0cd85fdc74d4ae9b9ff35300966b Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sun, 8 Oct 2023 21:28:14 -0400
Subject: [PATCH 014/135] handle uncategorized budget
---
code/budget_update.py | 23 ++++++++++++++++++++++-
code/helper.py | 12 ++++++++++++
user.properties | 1 -
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/code/budget_update.py b/code/budget_update.py
index a601d3cb5..a1154c3b8 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -85,9 +85,20 @@ def post_overall_amount_input(message, bot):
if str(chat_id) not in user_list:
user_list[str(chat_id)] = helper.createNewUserRecord()
user_list[str(chat_id)]["budget"]["overall"] = amount_value
+ total_budget = 0
+ for c in helper.getCategoryBudget(chat_id).values():
+ total_budget += float(c)
+ if total_budget > float(amount_value):
+ raise Exception("Overall budget cannot be less than " + str(total_budget))
+ uncategorized_budget = helper.get_uncategorized_amount(chat_id, amount_value)
+ if float(uncategorized_budget) > 0:
+ if user_list[str(chat_id)]["budget"]["category"] is None:
+ user_list[str(chat_id)]["budget"]["category"] = {}
+ user_list[str(chat_id)]["budget"]["category"]["uncategorized"] = uncategorized_budget
helper.write_json(user_list)
bot.send_message(chat_id, "Budget Updated!")
budget_view.display_overall_budget(message, bot)
+ print(user_list)
return user_list
except Exception as e:
helper.throw_exception(e, message, bot, logging)
@@ -186,7 +197,16 @@ def post_category_amount_input(message, bot, category):
user_list[str(chat_id)]["budget"]["category"] = {}
user_list[str(chat_id)]["budget"]["category"][category] = amount_value
if(user_list[str(chat_id)]["budget"]["overall"]):
- user_list[str(chat_id)]["budget"]["overall"] = str(round(float(user_list[str(chat_id)]["budget"]["overall"]) + float(amount_value),2))
+ if round(float(user_list[str(chat_id)]["budget"]["category"]["uncategorized"]) - float(amount_value),2) > 0:
+ user_list[str(chat_id)]["budget"]["category"]["uncategorized"] = str(round(float(user_list[str(chat_id)]["budget"]["category"]["uncategorized"]) - float(amount_value),2))
+ else:
+ user_list[str(chat_id)]["budget"]["category"]["uncategorized"] = str(0)
+ helper.write_json(user_list)
+ total_budget = 0
+ for c in helper.getCategoryBudget(chat_id).values():
+ total_budget += float(c)
+ print(total_budget)
+ user_list[str(chat_id)]["budget"]["overall"] = str(total_budget)
else:
user_list[str(chat_id)]["budget"]["overall"] = amount_value
helper.write_json(user_list)
@@ -194,6 +214,7 @@ def post_category_amount_input(message, bot, category):
chat_id, "Budget for " + category + " is now: $" + amount_value
)
budget_view.display_overall_budget(message, bot)
+ print(user_list)
post_category_add(message, bot)
except Exception as e:
diff --git a/code/helper.py b/code/helper.py
index 70c8c48d4..a2251bc85 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -167,6 +167,18 @@ def isCategoryBudgetByCategoryAvailable(chatId, cat):
return False
return cat in data.keys()
+def get_uncategorized_amount(chatId, amount):
+ overall_budget = float(amount)
+ category_budget_data = getCategoryBudget(chatId)
+ if category_budget_data is None:
+ return amount
+ category_budget = 0
+ for c in category_budget_data.values():
+ category_budget += float(c)
+ uncategorized_budget = overall_budget - category_budget
+ return str(round(uncategorized_budget,2))
+
+
def display_remaining_budget(message, bot, cat):
print("inside")
diff --git a/user.properties b/user.properties
index 5e0945f57..8b1378917 100644
--- a/user.properties
+++ b/user.properties
@@ -1,2 +1 @@
-api_token=6457044894:AAEGBeWPY6cdZhLuNe_2UYNuoFatM2Fnsgo
\ No newline at end of file
From 136a84698947f2faaf0511ed7a461d85a59545d1 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Tue, 10 Oct 2023 13:14:52 -0400
Subject: [PATCH 015/135] update budget bugfix
---
code/budget_update.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/code/budget_update.py b/code/budget_update.py
index a1154c3b8..3e8a1ff07 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -86,10 +86,11 @@ def post_overall_amount_input(message, bot):
user_list[str(chat_id)] = helper.createNewUserRecord()
user_list[str(chat_id)]["budget"]["overall"] = amount_value
total_budget = 0
- for c in helper.getCategoryBudget(chat_id).values():
- total_budget += float(c)
- if total_budget > float(amount_value):
- raise Exception("Overall budget cannot be less than " + str(total_budget))
+ if helper.isCategoryBudgetAvailable(chat_id):
+ for c in helper.getCategoryBudget(chat_id).values():
+ total_budget += float(c)
+ if total_budget > float(amount_value):
+ raise Exception("Overall budget cannot be less than " + str(total_budget))
uncategorized_budget = helper.get_uncategorized_amount(chat_id, amount_value)
if float(uncategorized_budget) > 0:
if user_list[str(chat_id)]["budget"]["category"] is None:
From 7af0aa2f3a9c2be0dd513f67ad252497623a6ddf Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Tue, 10 Oct 2023 16:55:51 -0400
Subject: [PATCH 016/135] update add command and bugfixes
---
code/add.py | 4 ++++
code/budget_update.py | 9 +++++----
code/delete.py | 4 +++-
code/helper.py | 10 ++++++----
4 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/code/add.py b/code/add.py
index 906956ad1..e12ab6967 100644
--- a/code/add.py
+++ b/code/add.py
@@ -45,6 +45,10 @@ def post_append_spend(message, bot):
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
markup.row_width = 2
selected_category = message.text
+ chat_id = message.chat.id
+ allocated_categories = helper.getCategoryBudget(chat_id)
+ if selected_category not in allocated_categories.keys():
+ helper.updateBudgetCategory(chat_id,selected_category)
helper.spend_categories.append(selected_category)
for c in helper.getSpendCategories():
markup.add(c)
diff --git a/code/budget_update.py b/code/budget_update.py
index a1154c3b8..3e8a1ff07 100644
--- a/code/budget_update.py
+++ b/code/budget_update.py
@@ -86,10 +86,11 @@ def post_overall_amount_input(message, bot):
user_list[str(chat_id)] = helper.createNewUserRecord()
user_list[str(chat_id)]["budget"]["overall"] = amount_value
total_budget = 0
- for c in helper.getCategoryBudget(chat_id).values():
- total_budget += float(c)
- if total_budget > float(amount_value):
- raise Exception("Overall budget cannot be less than " + str(total_budget))
+ if helper.isCategoryBudgetAvailable(chat_id):
+ for c in helper.getCategoryBudget(chat_id).values():
+ total_budget += float(c)
+ if total_budget > float(amount_value):
+ raise Exception("Overall budget cannot be less than " + str(total_budget))
uncategorized_budget = helper.get_uncategorized_amount(chat_id, amount_value)
if float(uncategorized_budget) > 0:
if user_list[str(chat_id)]["budget"]["category"] is None:
diff --git a/code/delete.py b/code/delete.py
index 6b7f9f2c1..691d61ebe 100644
--- a/code/delete.py
+++ b/code/delete.py
@@ -32,5 +32,7 @@ def deleteHistory(chat_id):
"""
global user_list
if str(chat_id) in user_list:
- del user_list[str(chat_id)]
+ user_list[str(chat_id)]["data"] = []
+ user_list[str(chat_id)]["budget"]["overall"] = None
+ user_list[str(chat_id)]["budget"]["category"] = None
return user_list
diff --git a/code/helper.py b/code/helper.py
index a2251bc85..522167e8a 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -183,10 +183,8 @@ def get_uncategorized_amount(chatId, amount):
def display_remaining_budget(message, bot, cat):
print("inside")
chat_id = message.chat.id
- if isOverallBudgetAvailable(chat_id):
- display_remaining_overall_budget(message, bot)
- elif isCategoryBudgetByCategoryAvailable(chat_id, cat):
- display_remaining_category_budget(message, bot, cat)
+ display_remaining_category_budget(message, bot, cat)
+ display_remaining_overall_budget(message, bot)
def display_remaining_overall_budget(message, bot):
@@ -253,6 +251,10 @@ def calculate_total_spendings_for_category(queryResult, cat):
total = total + float(s[2])
return total
+def updateBudgetCategory(chatId, category):
+ user_list = read_json()
+ user_list[str(chatId)]["budget"]["category"][category] = str(0)
+ write_json(user_list)
def getSpendCategories():
"""
From 068801f02fa817f6d8059f05327c4760acff5478 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Wed, 11 Oct 2023 18:25:51 -0400
Subject: [PATCH 017/135] Reworked add expense workflow
---
code/add.py | 71 +++++++++++++++++++++++++++-----------------------
code/helper.py | 2 ++
2 files changed, 41 insertions(+), 32 deletions(-)
diff --git a/code/add.py b/code/add.py
index e12ab6967..22debd592 100644
--- a/code/add.py
+++ b/code/add.py
@@ -18,26 +18,29 @@ def run(message, bot):
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
- 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)
+ chat_id = message.chat.id
+ for c in helper.getSpendCategories():
+ markup.add(c)
+ markup.add("Add new category")
+ msg = bot.reply_to(message, "Select Category", reply_markup=markup)
+ bot.register_next_step_handler(msg, post_category_selection, bot)
-def post_user_def_category(message, bot):
- markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- markup.row_width = 2
- 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)
- 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)
+
+# def post_user_def_category(message, bot):
+# markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+# markup.row_width = 2
+# 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)
+# 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)
def post_append_spend(message, bot):
@@ -67,24 +70,28 @@ def post_category_selection(message, bot):
try:
chat_id = message.chat.id
selected_category = message.text
- if selected_category not in helper.getSpendCategories():
- bot.send_message(
- chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
+ if selected_category == "Add new category":
+ message1 = bot.send_message(chat_id, "Please enter your category")
+ bot.register_next_step_handler(message1, post_append_spend, bot)
+ else:
+ if selected_category not in helper.getSpendCategories():
+ bot.send_message(
+ chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
+ )
+ raise Exception(
+ 'Sorry I don\'t recognise this category "{}"!'.format(selected_category)
+ )
+
+ option[chat_id] = selected_category
+ message = bot.send_message(
+ chat_id,
+ "How much did you spend on {}? \n(Enter numeric values only)".format(
+ str(option[chat_id])
+ ),
)
- raise Exception(
- 'Sorry I don\'t recognise this category "{}"!'.format(selected_category)
+ bot.register_next_step_handler(
+ message, post_amount_input, bot, selected_category
)
-
- option[chat_id] = selected_category
- message = bot.send_message(
- chat_id,
- "How much did you spend on {}? \n(Enter numeric values only)".format(
- str(option[chat_id])
- ),
- )
- bot.register_next_step_handler(
- message, post_amount_input, bot, selected_category
- )
except Exception as e:
logging.exception(str(e))
bot.reply_to(message, "Oh no! " + str(e))
diff --git a/code/helper.py b/code/helper.py
index 522167e8a..8965ba158 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -222,6 +222,8 @@ def calculate_total_spendings(queryResult):
def display_remaining_category_budget(message, bot, cat):
chat_id = message.chat.id
+ if not getCategoryBudgetByCategory(chat_id,cat):
+ updateBudgetCategory(chat_id, cat)
remaining_budget = calculateRemainingCategoryBudget(chat_id, cat)
if remaining_budget >= 0:
msg = "\nRemaining Budget for " + cat + " is $" + str(remaining_budget)
From 9ca00caaf23b1c9410b586b8a00cb427a7a90e7c Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Fri, 13 Oct 2023 05:14:19 +0530
Subject: [PATCH 018/135] Update README.md
---
README.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 8e27f5f57..a9b45d7af 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,10 @@
Table of Contents
- - Why should you use Dollar Bot?
- - Check out the video!
- - What is new in this version?
+ - What's DollarBot?
+
- Why use DollarBot?
+
+ - What is new in this version?
- Installation
- Testing
- Code Coverage
From 8bb884f1076e526610f0ff7513f4cf468e3d2201 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Fri, 13 Oct 2023 05:30:43 +0530
Subject: [PATCH 019/135] Update README.md
Installation Steps done
---
README.md | 51 +++++++++++++++++++++++++++++++++++++--------------
1 file changed, 37 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index a9b45d7af..500dab01e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ๐ฐ MyDollar Bot ๐ฐ
+# ๐ฐ Dollar Bot ๐ฐ
@@ -23,7 +23,7 @@
+ src="https://s1.mzstatic.com/us/r30/Purple1/v4/de/ab/45/deab454d-8881-b37d-9513-b0e26424cc57/pr_source.png?downloadKey=1425248837_8a393efcc4a821cbf9639d5570f8e966" alt="Dollar Bot">
@@ -43,9 +43,9 @@
-## Why should you use MyDollar Bot?
+## Why use DollarBot?
-Dollar Bot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle.
+DollarBot 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
@@ -58,24 +58,47 @@ With simple commands, this bot allows you to:
[](https://youtu.be/aCjcT1CHAzU)
-## What is new in this version?
+## What's new?
+
+<#TODO: short description here>
Checkout the [this documentation](https://github.com/usmanwardag/dollar_bot/blob/main/docs/Updated_version.pdf)
+
## Installation
-The below instructions can be followed in order to set-up this bot at your end in a span of few minutes! Let's get started:
+#### Pre-requisite: The Telegram Desktop App
-1. Clone this repository to your local system.
+Since DollarBot is built on top of Telegram, you'll first need:
+1. Download the Telegram Desktop Application here.
+```https://desktop.telegram.org/```
+2. Create a Telegram account or Sign in.
-2. Start a terminal session in the directory where the project has been cloned. Run the following command to install the required dependencies:
-```
- pip install -r requirements.txt
+Open up your terminal and let's get started:
+### MacOS / Ubuntu Users
+
+1. Clone this repository to your local system.
+```
+ git clone https://github.com/aditikilledar/dollar_bot_SE23/
+```
+2. Start a terminal session in the directory where the project has been cloned. Run the following command and follow the instructions on-screen to complete the installation.
```
-3. Download and install the Telegram desktop application for your system from the following site:
- ```
- https://desktop.telegram.org/
- ```
+ bash setup.sh
+```
+
+There, all done!
+
+Our bash scripts for installation are easy to run on MacOS or on UNIX terminals.
+
+### Windows
+
+With Windows, you'll need to use Cygwin.
+Cygwin is:
+ a large collection of GNU and Open Source tools which provide functionality similar to a Linux distribution on Windows.
+ a DLL (cygwin1.dll) which provides substantial POSIX API functionality.
+(source: cygwin.com)
+
+Additionally, find more hints on installation here.
## How to run?
1. In Telegram, search for "BotFather". Click on "Start", and enter the following command:
From 4caf8eeb1661f2faab7dedfda37c3aac3c83bb5f Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Fri, 13 Oct 2023 05:34:54 +0530
Subject: [PATCH 020/135] Update README.md
---
README.md | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 500dab01e..995013a29 100644
--- a/README.md
+++ b/README.md
@@ -20,13 +20,6 @@
-
-
-
-
-
-
[](https://GitHub.com/usmanwardag/auto_anki)

@@ -43,6 +36,11 @@
+## What's DollarBot?
+
+
+TODO
+
## Why use DollarBot?
DollarBot is an easy-to-use Telegram Bot that assists you in recording your daily expenses on a local system without any hassle.
From 415b1f472669ac80b54c5117ac21e683883efa70 Mon Sep 17 00:00:00 2001
From: Srinath Srinivasan
Date: Thu, 12 Oct 2023 21:38:17 -0400
Subject: [PATCH 021/135] pushing initial analytics feat
Pushing initial implementation of analytics feature. (Incomplete)
---
code/analytics.py | 45 ++++++++++++++++++++++++++++++++++++++++++++
code/code.py | 10 ++++++++++
code/get_analysis.py | 19 +++++++++++++++++++
code/helper.py | 5 +++++
4 files changed, 79 insertions(+)
create mode 100644 code/analytics.py
create mode 100644 code/get_analysis.py
diff --git a/code/analytics.py b/code/analytics.py
new file mode 100644
index 000000000..c05e743c1
--- /dev/null
+++ b/code/analytics.py
@@ -0,0 +1,45 @@
+import helper
+import logging
+import types
+import get_analysis
+
+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 view analytics
+ 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
+ for c in options.values():
+ markup.add(c)
+ msg = bot.reply_to(message, "Select the type of analysis (grouped by catefgory):", 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 analytics.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
+ op = message.text
+ options = helper.getAnalyticsOptions()
+ 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"]:
+ get_analysis.viewOverallBudget(chat_id, bot)
+ # elif op == options["spend"]:
+ # get_analysis.viewSpendWise(chat_id, bot)
+ # elif op == options["remaining"]:
+ # get_analysis.viewRemaining(chat_id, bot)
+ except Exception as e:
+ helper.throw_exception(e, message, bot, logging)
\ No newline at end of file
diff --git a/code/code.py b/code/code.py
index 10bc43540..bcf8fd208 100644
--- a/code/code.py
+++ b/code/code.py
@@ -12,6 +12,7 @@
import delete
import add
import budget
+import analytics
from datetime import datetime
from jproperties import Properties
@@ -219,6 +220,15 @@ def command_delete(message):
def command_budget(message):
budget.run(message, bot)
+# handles analytics command
+@bot.message_handler(commands=["analytics"])
+def command_analytics(message):
+ """
+ command_analytics(message): Take an argument message with content and chat ID. Calls analytics to
+ run analytics. Commands to run this commands=["analytics"]
+ """
+ analytics.run(message, bot)
+
# not used
diff --git a/code/get_analysis.py b/code/get_analysis.py
new file mode 100644
index 000000000..429ca1893
--- /dev/null
+++ b/code/get_analysis.py
@@ -0,0 +1,19 @@
+import helper
+import logging
+import types
+import matplotlib.pyplot as plt
+import time
+
+def viewOverallBudget(chat_id, bot):
+ if not helper.isCategoryBudgetAvailable():
+ bot.send_message(chat_id, "No category budget available", reply_markup=types.ReplyKeyboardRemove())
+ category_budget = {}
+ for cat in helper.spend_categories:
+ category_budget[cat] = helper.getCategoryBudgetByCategory(chat_id, cat)
+
+ fig, ax = plt.subplots()
+ ax.pie(category_budget.values, labels=category_budget.keys, autopct='%1.1f%%')
+ random_time = time.time()
+ img_name = str(random_time)+".png"
+ plt.savefig(img_name)
+ bot.send_photo(chat_id, img_name, reply_markup=types.ReplyKeyboardRemove())
diff --git a/code/helper.py b/code/helper.py
index 522167e8a..0083532be 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -24,6 +24,8 @@
data_format = {"data": [], "budget": {"overall": None, "category": None}}
+analytics_options = {"overall": "Overall budget split", "spend": "Split of current spend", "remaining": "Remaining value"}
+
# set of implemented commands and their description
commands = {
"help": "Display the list of commands.",
@@ -316,3 +318,6 @@ def getBudgetTypes():
def getUpdateOptions():
return update_options
+
+def getAnalyticsOptions():
+ return analytics_options
From f353889843f4a51697193f3e472080ed45808008 Mon Sep 17 00:00:00 2001
From: Srinath Srinivasan
Date: Thu, 12 Oct 2023 22:08:31 -0400
Subject: [PATCH 022/135] added fix for graph
---
code/analytics.py | 6 +++---
code/get_analysis.py | 11 ++++++-----
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/code/analytics.py b/code/analytics.py
index c05e743c1..ac4a8d722 100644
--- a/code/analytics.py
+++ b/code/analytics.py
@@ -1,6 +1,6 @@
import helper
import logging
-import types
+from telebot import types
import get_analysis
def run(message, bot):
@@ -12,11 +12,11 @@ def run(message, bot):
telegram bot object from the main code.py function.
"""
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
- options = helper.getBudgetOptions()
+ options = helper.getAnalyticsOptions()
markup.row_width = 2
for c in options.values():
markup.add(c)
- msg = bot.reply_to(message, "Select the type of analysis (grouped by catefgory):", reply_markup=markup)
+ msg = bot.reply_to(message, "Select the type of analysis (grouped by category):", reply_markup=markup)
bot.register_next_step_handler(msg, post_operation_selection, bot)
def post_operation_selection(message, bot):
diff --git a/code/get_analysis.py b/code/get_analysis.py
index 429ca1893..cc6e8f05f 100644
--- a/code/get_analysis.py
+++ b/code/get_analysis.py
@@ -1,19 +1,20 @@
import helper
import logging
-import types
+from telebot import types
import matplotlib.pyplot as plt
import time
def viewOverallBudget(chat_id, bot):
- if not helper.isCategoryBudgetAvailable():
+ if not helper.isCategoryBudgetAvailable(chat_id):
bot.send_message(chat_id, "No category budget available", reply_markup=types.ReplyKeyboardRemove())
category_budget = {}
for cat in helper.spend_categories:
- category_budget[cat] = helper.getCategoryBudgetByCategory(chat_id, cat)
+ if helper.isCategoryBudgetByCategoryAvailable(chat_id, cat):
+ category_budget[cat] = helper.getCategoryBudgetByCategory(chat_id, cat)
fig, ax = plt.subplots()
- ax.pie(category_budget.values, labels=category_budget.keys, autopct='%1.1f%%')
+ ax.pie(category_budget.values(), labels=category_budget.keys(), autopct='%1.1f%%')
random_time = time.time()
img_name = str(random_time)+".png"
plt.savefig(img_name)
- bot.send_photo(chat_id, img_name, reply_markup=types.ReplyKeyboardRemove())
+ bot.send_photo(chat_id, photo=open(img_name, 'rb'), reply_markup=types.ReplyKeyboardRemove())
\ No newline at end of file
From e6ebf55a4483249cab9c67b87b654c88a37cf7ab Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Fri, 13 Oct 2023 10:00:33 -0400
Subject: [PATCH 023/135] implemented suggested changes in add expenses
workflow
---
code/add.py | 19 ++-----------------
1 file changed, 2 insertions(+), 17 deletions(-)
diff --git a/code/add.py b/code/add.py
index 22debd592..e68b7f635 100644
--- a/code/add.py
+++ b/code/add.py
@@ -28,21 +28,6 @@ def run(message, bot):
bot.register_next_step_handler(msg, post_category_selection, bot)
-
-# def post_user_def_category(message, bot):
-# markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
-# markup.row_width = 2
-# 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)
-# 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)
-
-
def post_append_spend(message, bot):
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
@@ -79,13 +64,13 @@ def post_category_selection(message, bot):
chat_id, "Invalid", reply_markup=types.ReplyKeyboardRemove()
)
raise Exception(
- 'Sorry I don\'t recognise this category "{}"!'.format(selected_category)
+ 'Sorry, I don\'t recognise this category "{}"!'.format(selected_category)
)
option[chat_id] = selected_category
message = bot.send_message(
chat_id,
- "How much did you spend on {}? \n(Enter numeric values only)".format(
+ "How much did you spend on {}? \n(Numeric values only)".format(
str(option[chat_id])
),
)
From 5f77295cfd2d3f349b5e6428e31ca63e0a9d989c Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 04:47:18 +0530
Subject: [PATCH 024/135] Update README.md with run_forever instructions
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 995013a29..820320bbd 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
What is new in this version?
Installation
+ For Developers and Future Contributors
Testing
Code Coverage
License
@@ -32,7 +33,6 @@
[](https://github.com/sak007/MyDollarBot-BOTGo/issues?q=is%3Aopen+is%3Aissue)
[](https://github.com/sak007/MyDollarBot-BOTGo/issues?q=is%3Aissue+is%3Aclosed)
-
@@ -126,6 +126,7 @@ A successful run will generate a message on your terminal that says "TeleBot: St
### Run Automatically at Startup
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.
+Click here for help adding to .bashrc files.
## Testing
From 1ea144ee640d3744f4a43c1a68269d6133e5a165 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 04:48:34 +0530
Subject: [PATCH 025/135] Create Developer_ReadMe.md
For testing, contribution, code of conduct and other docu not related to setup, running and installation.
---
Developer_ReadMe.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Developer_ReadMe.md
diff --git a/Developer_ReadMe.md b/Developer_ReadMe.md
new file mode 100644
index 000000000..0124c21cd
--- /dev/null
+++ b/Developer_ReadMe.md
@@ -0,0 +1 @@
+# For Developers: By the Developers of DollarBot
From 563f0f562cbe451a1c48cf5bd652a0b0904ed57b Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 05:41:41 +0530
Subject: [PATCH 026/135] Update README.md as only User ReadMe
---
README.md | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 820320bbd..e97df9cfd 100644
--- a/README.md
+++ b/README.md
@@ -4,18 +4,12 @@
Table of Contents
- - What's DollarBot?
+
- What's DollarBot?
- Why use DollarBot?
- What is new in this version?
- Installation
- - For Developers and Future Contributors
- - Testing
- - Code Coverage
- - License
- - Code Documentation
- - How to Contribute
- - Future RoadMap
+ - For Developers and Future Contributors
- Contributors
- Acknowledgements
From 9b0710527b57b59b5b2f0f28a2d775aedd43e8d7 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 05:41:48 +0530
Subject: [PATCH 027/135] Update Developer_ReadMe.md as dev readme
---
Developer_ReadMe.md | 50 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/Developer_ReadMe.md b/Developer_ReadMe.md
index 0124c21cd..f0f406f0f 100644
--- a/Developer_ReadMe.md
+++ b/Developer_ReadMe.md
@@ -1 +1,51 @@
# For Developers: By the Developers of DollarBot
+
+
+ Table of Contents
+
+ - Testing
+ - Code Coverage
+ - License
+ - Code Documentation
+ - How to Contribute
+ - Future RoadMap
+
+
+
+## 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:
+```
+python run -m pytest test/
+```
+
+## Code Coverage
+
+Code coverage is part of the build. Every time new code is pushed to the repository, the build is run, and along with it, code coverage is computed. This can be viewed by selecting the build, and then choosing the codecov pop-up on hover.
+
+Locally, we use the coverage package in python for code coverage. The commands to check code coverage in python are as follows:
+
+```
+coverage run -m pytest test/
+coverage report
+```
+
+## 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.
+
+
+## Code Documentation
+
+Checkout the [docs](https://github.com/sak007/MyDollarBot-BOTGo/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)
+
+For any feedback, issues, or bug reports, please create an issue [here](https://github.com/usmanwardag/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.
From 3927cfd7b85fcaac351d12ea9649987d50e48488 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 05:43:27 +0530
Subject: [PATCH 028/135] Update README.md
---
README.md | 42 +++---------------------------------------
1 file changed, 3 insertions(+), 39 deletions(-)
diff --git a/README.md b/README.md
index e97df9cfd..327663e35 100644
--- a/README.md
+++ b/README.md
@@ -9,12 +9,14 @@
What is new in this version?
Installation
- For Developers and Future Contributors
+
Contributors
Acknowledgements
+Are you a developer? Click here: For Developers and Future Contributors
+
[](https://GitHub.com/usmanwardag/auto_anki)

@@ -122,44 +124,6 @@ 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.
Click here for help adding to .bashrc files.
-## 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:
-```
-python run -m pytest test/
-```
-
-## Code Coverage
-
-Code coverage is part of the build. Every time new code is pushed to the repository, the build is run, and along with it, code coverage is computed. This can be viewed by selecting the build, and then choosing the codecov pop-up on hover.
-
-Locally, we use the coverage package in python for code coverage. The commands to check code coverage in python are as follows:
-
-```
-coverage run -m pytest test/
-coverage report
-```
-
-## 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.
-
-
-## Code Documentation
-
-Checkout the [docs](https://github.com/sak007/MyDollarBot-BOTGo/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)
-
-For any feedback, issues, or bug reports, please create an issue [here](https://github.com/usmanwardag/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.
-
## Contributors
From 3af44d800bd5d8ed7985c9ab67ba10ed7c06e17f Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sat, 14 Oct 2023 05:45:34 +0530
Subject: [PATCH 029/135] Update README.md for Table of Contents
---
README.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 327663e35..36f9a2ae7 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
# ๐ฐ Dollar Bot ๐ฐ
-
- Table of Contents
+Table of Contents
- What's DollarBot?
- Why use DollarBot?
@@ -13,7 +12,6 @@
- Contributors
- Acknowledgements
-
Are you a developer? Click here: For Developers and Future Contributors
From e2c32024fa03493116b5db35025ec6d978602ff2 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Fri, 13 Oct 2023 21:23:17 -0400
Subject: [PATCH 030/135] added recurring expenses functionality
---
code/add.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++----
code/helper.py | 8 ++++++
2 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/code/add.py b/code/add.py
index e68b7f635..1c0e8b05b 100644
--- a/code/add.py
+++ b/code/add.py
@@ -37,7 +37,7 @@ def post_append_spend(message, bot):
allocated_categories = helper.getCategoryBudget(chat_id)
if selected_category not in allocated_categories.keys():
helper.updateBudgetCategory(chat_id,selected_category)
- helper.spend_categories.append(selected_category)
+ helper.spend_categories.insert(0,selected_category)
for c in helper.getSpendCategories():
markup.add(c)
msg = bot.reply_to(message, "Select Category", reply_markup=markup)
@@ -68,15 +68,18 @@ def post_category_selection(message, bot):
)
option[chat_id] = selected_category
- message = bot.send_message(
+ expense_history = helper.getUserHistoryByCategory(chat_id,selected_category)
+ if expense_history:
+ recur_msg = bot.send_message(chat_id,"You have previously recorded expenses in this category. Do you want to repeat one of these expenses?(Y/N)")
+ bot.register_next_step_handler(recur_msg, record_expense, bot, expense_history)
+ else:
+ message = bot.send_message(
chat_id,
"How much did you spend on {}? \n(Numeric values only)".format(
str(option[chat_id])
),
)
- bot.register_next_step_handler(
- message, post_amount_input, bot, selected_category
- )
+ bot.register_next_step_handler(message, post_amount_input, bot, selected_category)
except Exception as e:
logging.exception(str(e))
bot.reply_to(message, "Oh no! " + str(e))
@@ -93,6 +96,69 @@ def post_category_selection(message, bot):
bot.send_message(chat_id, display_text)
+def record_expense(message, bot, previous_expenses=None, category=None):
+ print("In function to record expense")
+ chat_id = message.chat.id
+ selection = message.text
+ print(selection)
+ if selection == "Y" or selection == "y":
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ chat_id = message.chat.id
+ for record in previous_expenses:
+ markup.add(record)
+ msg = bot.reply_to(message, "Select the expense you want to repeat", reply_markup=markup)
+ bot.register_next_step_handler(msg, post_expense_selection, bot)
+ else:
+ message = bot.send_message(
+ chat_id,
+ "How much did you spend on {}? \n(Numeric values only)".format(
+ str(option[chat_id])
+ ),
+ )
+ bot.register_next_step_handler(
+ message, post_amount_input, bot, category
+ )
+
+def post_expense_selection(message,bot):
+ chat_id = message.chat.id
+ expense_record = message.text
+ expense_data = expense_record.split(",")
+ amount = expense_data[2]
+ category = expense_data[1]
+ print(amount)
+ amount_value = helper.validate_entered_amount(amount) # validate
+ try:
+ if amount_value == 0: # cannot be $0 spending
+ raise Exception("Spent amount has to be a non-zero number.")
+
+ date_of_entry = datetime.today().strftime(
+ helper.getDateFormat() + " " + helper.getTimeFormat()
+ )
+
+ date_str, category_str, amount_str = (
+ str(date_of_entry),
+ str(category),
+ str(amount_value),
+ )
+
+ helper.write_json(
+ add_user_record(
+ chat_id, "{},{},{}".format(date_str, category_str, amount_str)
+ )
+ )
+
+ bot.send_message(
+ chat_id,
+ "The following expenditure has been recorded: You have spent ${} for {} on {}".format(
+ amount_str, category_str, date_str
+ ),
+ )
+ helper.display_remaining_budget(message, bot, category)
+ except Exception as e:
+ logging.exception(str(e))
+ bot.reply_to(message, "Oh no. " + str(e))
+
def post_amount_input(message, bot, selected_category):
"""
post_amount_input(message, bot): It takes 2 arguments for processing -
diff --git a/code/helper.py b/code/helper.py
index 8965ba158..640bb9b1d 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -109,6 +109,14 @@ def getUserHistory(chat_id):
return data["data"]
return None
+def getUserHistoryByCategory(chat_id, category):
+ data = getUserHistory(chat_id)
+ previous_expenses = []
+ for record in data:
+ if f",{category}," in record:
+ previous_expenses.append(record)
+ return previous_expenses
+
def getUserData(chat_id):
user_list = read_json()
From f5536c9b188e6ebb85b817ad6e2b8a4e8a313694 Mon Sep 17 00:00:00 2001
From: Srinath Srinivasan
Date: Sat, 14 Oct 2023 18:05:25 -0400
Subject: [PATCH 031/135] Completed analytics functionality
---
code/analytics.py | 8 +++---
code/get_analysis.py | 63 +++++++++++++++++++++++++++++++++++++++++++-
code/helper.py | 13 +++++++++
3 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/code/analytics.py b/code/analytics.py
index ac4a8d722..bb41c5db1 100644
--- a/code/analytics.py
+++ b/code/analytics.py
@@ -37,9 +37,9 @@ def post_operation_selection(message, bot):
raise Exception('Sorry I don\'t recognise this operation "{}"!'.format(op))
if op == options["overall"]:
get_analysis.viewOverallBudget(chat_id, bot)
- # elif op == options["spend"]:
- # get_analysis.viewSpendWise(chat_id, bot)
- # elif op == options["remaining"]:
- # get_analysis.viewRemaining(chat_id, bot)
+ elif op == options["spend"]:
+ get_analysis.viewSpendWise(chat_id, bot)
+ elif op == options["remaining"]:
+ get_analysis.viewRemaining(chat_id, bot)
except Exception as e:
helper.throw_exception(e, message, bot, logging)
\ No newline at end of file
diff --git a/code/get_analysis.py b/code/get_analysis.py
index cc6e8f05f..01a519160 100644
--- a/code/get_analysis.py
+++ b/code/get_analysis.py
@@ -3,6 +3,7 @@
from telebot import types
import matplotlib.pyplot as plt
import time
+import numpy as np
def viewOverallBudget(chat_id, bot):
if not helper.isCategoryBudgetAvailable(chat_id):
@@ -12,8 +13,68 @@ def viewOverallBudget(chat_id, bot):
if helper.isCategoryBudgetByCategoryAvailable(chat_id, cat):
category_budget[cat] = helper.getCategoryBudgetByCategory(chat_id, cat)
- fig, ax = plt.subplots()
+ _, ax = plt.subplots()
ax.pie(category_budget.values(), labels=category_budget.keys(), autopct='%1.1f%%')
+ random_time = time.time()
+ img_name = str(random_time)+".png"
+ plt.savefig(img_name)
+ bot.send_photo(chat_id, photo=open(img_name, 'rb'), reply_markup=types.ReplyKeyboardRemove())
+
+def viewSpendWise(chat_id, bot):
+ category_spend = {}
+ for cat in helper.spend_categories:
+ spend = helper.calculate_total_spendings_for_cateogory_chat_id(chat_id,cat)
+ if spend != 0:
+ category_spend[cat] = spend
+
+ if category_spend.keys() == None:
+ bot.send_message(chat_id, "No category spend available", reply_markup=types.ReplyKeyboardRemove())
+
+ _, ax = plt.subplots()
+ ax.pie(category_spend.values(), labels=category_spend.keys(), autopct='%1.1f%%')
+ random_time = time.time()
+ img_name = str(random_time)+".png"
+ plt.savefig(img_name)
+ bot.send_photo(chat_id, photo=open(img_name, 'rb'), reply_markup=types.ReplyKeyboardRemove())
+
+def viewRemaining(chat_id, bot):
+ if not helper.isCategoryBudgetAvailable(chat_id):
+ bot.send_message(chat_id, "No category budget available", reply_markup=types.ReplyKeyboardRemove())
+
+ category_spend_percent = {}
+ for cat in helper.spend_categories:
+ if helper.isCategoryBudgetByCategoryAvailable(chat_id, cat):
+ percent = helper.calculateRemainingCateogryBudgetPercent(chat_id, cat)
+ category_spend_percent[cat] = percent
+
+ labels = tuple(category_spend_percent.keys())
+ print(labels)
+ type(labels)
+
+ remaining_val_list = [100 - x for x in list(category_spend_percent.values())]
+
+ weight_counts = {
+ "Used": list(category_spend_percent.values()),
+ "Remaining": remaining_val_list,
+ }
+ print(weight_counts)
+
+ width = 0.5
+
+ _, ax = plt.subplots()
+ # simply impossible to submit anything other than an np ndarray here
+ # matplotlib simply fails to recognize shape of any other "array-like"
+ #object
+ bottom = np.zeros(len(list(category_spend_percent.values())))
+
+ for boolean, weight_count in weight_counts.items():
+ print(boolean, weight_count)
+ ax.bar(labels, weight_count, width, label=boolean, bottom=bottom)
+ bottom += weight_count
+
+ ax.set_title("Category-wise budget consumed")
+ ax.legend(loc="upper right")
+
random_time = time.time()
img_name = str(random_time)+".png"
plt.savefig(img_name)
diff --git a/code/helper.py b/code/helper.py
index 725b998cb..dac7919e8 100644
--- a/code/helper.py
+++ b/code/helper.py
@@ -253,6 +253,13 @@ def calculateRemainingCategoryBudget(chat_id, cat):
return float(budget) - calculate_total_spendings_for_category(queryResult, cat)
+def calculateRemainingCateogryBudgetPercent(chat_id, cat):
+ budget = getCategoryBudgetByCategory(chat_id, cat)
+ history = getUserHistory(chat_id)
+ query = datetime.now().today().strftime(getMonthFormat())
+ queryResult = [value for index, value in enumerate(history) if str(query) in value]
+
+ return (calculate_total_spendings_for_category(queryResult, cat)/float(budget))*100
def calculate_total_spendings_for_category(queryResult, cat):
total = 0
@@ -263,6 +270,12 @@ def calculate_total_spendings_for_category(queryResult, cat):
total = total + float(s[2])
return total
+def calculate_total_spendings_for_cateogory_chat_id(chat_id, cat):
+ history = getUserHistory(chat_id)
+ query = datetime.now().today().strftime(getMonthFormat())
+ queryResult = [value for index, value in enumerate(history) if str(query) in value]
+ return calculate_total_spendings_for_category(queryResult, cat)
+
def updateBudgetCategory(chatId, category):
user_list = read_json()
user_list[str(chatId)]["budget"]["category"][category] = str(0)
From 7bc6f4295dff1728381ac41a289a8e5c9c445072 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sat, 14 Oct 2023 22:47:46 -0400
Subject: [PATCH 032/135] edit command issue resolved
---
code/edit.py | 57 ++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 46 insertions(+), 11 deletions(-)
diff --git a/code/edit.py b/code/edit.py
index ce0419734..508512c22 100644
--- a/code/edit.py
+++ b/code/edit.py
@@ -33,10 +33,11 @@ def select_category_to_be_updated(m, bot):
for c in selected_data:
markup.add(c.strip())
choice = bot.reply_to(m, "What do you want to update?", reply_markup=markup)
- bot.register_next_step_handler(choice, enter_updated_data, bot, selected_data)
+ updated = []
+ bot.register_next_step_handler(choice, enter_updated_data, bot, selected_data, updated)
-def enter_updated_data(m, bot, selected_data):
+def enter_updated_data(m, bot, selected_data, updated):
choice1 = "" if m.text is None else m.text
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
@@ -62,27 +63,38 @@ def cal(c):
)
elif result:
- edit_date(bot, selected_data, result, c.message.chat.id)
+ edit_date(bot, selected_data, result, c, updated)
bot.edit_message_text(
f"Date is updated: {result}",
c.message.chat.id,
c.message.message_id,
)
+
if "Category" in choice1:
-
new_cat = bot.reply_to(m, "Please select the new category", reply_markup=markup)
- bot.register_next_step_handler(new_cat, edit_cat, bot, selected_data)
+ bot.register_next_step_handler(new_cat, edit_cat, bot, selected_data, updated)
if "Amount" in choice1:
new_cost = bot.reply_to(
m, "Please type the new cost\n(Enter only numerical value)"
)
- bot.register_next_step_handler(new_cost, edit_cost, bot, selected_data)
+ bot.register_next_step_handler(new_cost, edit_cost, bot, selected_data, updated)
+
+def update_different_category(m, bot, selected_data, updated):
+ response = m.text
+ if response == "Y" or response == "y":
+ markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
+ markup.row_width = 2
+ for c in selected_data:
+ if c not in updated:
+ markup.add(c.strip())
+ choice = bot.reply_to(m, "What do you want to update?", reply_markup=markup)
+ bot.register_next_step_handler(choice, enter_updated_data, bot, selected_data, updated)
-def edit_date(bot, selected_data, result, chat_id):
+def edit_date(bot, selected_data, result, c, updated):
"""
def edit_date(m, 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
@@ -90,8 +102,8 @@ def edit_date(m, bot): It takes 2 arguments for processing - message which is
"""
user_list = helper.read_json()
new_date = str(result)
-
- chat_id = chat_id
+ chat_id = c.message.chat.id
+ m = c.message
data_edit = helper.getUserHistory(chat_id)
for i in range(len(data_edit)):
@@ -109,9 +121,17 @@ def edit_date(m, bot): It takes 2 arguments for processing - message which is
user_list[str(chat_id)]["data"] = data_edit
helper.write_json(user_list)
+ new_date_str = "Date=" + new_date
+ updated.append(new_date_str)
+ selected_data[0] = new_date_str
+ if len(updated) == 3:
+ bot.send_message(m.chat.id, "You have updated all the categories for this expense")
+ return
+ resp = bot.send_message(m.chat.id, "Do you want to update another category in this expense?(Y/N)")
+ bot.register_next_step_handler(resp, update_different_category, bot, selected_data ,updated)
-def edit_cat(m, bot, selected_data):
+def edit_cat(m, bot, selected_data, updated):
"""
def edit_cat(m, 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 edit3(m, bot):: function in the
@@ -134,10 +154,18 @@ def edit_cat(m, bot): It takes 2 arguments for processing - message which is the
user_list[str(chat_id)]["data"] = data_edit
helper.write_json(user_list)
+ new_cat_str = "Category=" + new_cat
+ updated.append(new_cat_str)
+ selected_data[1] = new_cat_str
bot.reply_to(m, "Category is updated")
+ if len(updated) == 3:
+ bot.send_message(m.chat.id, "You have updated all the categories for this expense")
+ return
+ resp = bot.send_message(m.chat.id, "Do you want to update another category in this expense?(Y/N)")
+ bot.register_next_step_handler(resp, update_different_category, bot, selected_data, updated)
-def edit_cost(m, bot, selected_data):
+def edit_cost(m, bot, selected_data, updated):
"""
def edit_cost(m, 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
@@ -164,4 +192,11 @@ def edit_cost(m, bot): It takes 2 arguments for processing - message which is th
bot.reply_to(m, "Expense amount is updated")
else:
bot.reply_to(m, "The cost is invalid")
+ new_cost_str = "Category=" + new_cost
+ updated.append(new_cost_str)
+ selected_data[1] = new_cost_str
+ if len(updated) == 3:
+ bot.send_message(m.chat.id, "You have updated all the categories for this expense")
return
+ resp = bot.send_message(m.chat.id, "Do you want to update another category in this expense?(Y/N)")
+ bot.register_next_step_handler(resp, update_different_category, bot, selected_data, updated)
From 70079ed3d8dbde57d262ea7def3bdc716efdc978 Mon Sep 17 00:00:00 2001
From: shashank-madan
Date: Sat, 14 Oct 2023 23:13:50 -0400
Subject: [PATCH 033/135] edge case bugfix
---
code/edit.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/code/edit.py b/code/edit.py
index 508512c22..6973b8b85 100644
--- a/code/edit.py
+++ b/code/edit.py
@@ -15,7 +15,11 @@ def run(m, bot):
chat_id = m.chat.id
markup = types.ReplyKeyboardMarkup(one_time_keyboard=True)
markup.row_width = 2
- for c in helper.getUserHistory(chat_id):
+ user_history = helper.getUserHistory(chat_id)
+ if not user_history:
+ bot.send_message(chat_id,"You have no previously recorded expenses to modify")
+ return
+ for c in user_history:
expense_data = c.split(",")
str_date = "Date=" + expense_data[0]
str_category = ",\t\tCategory=" + expense_data[1]
From f900ff2901c128916933e20ce3bc112122d76e04 Mon Sep 17 00:00:00 2001
From: Aditi Killedar <73051765+aditikilledar@users.noreply.github.com>
Date: Sun, 15 Oct 2023 20:02:13 +0530
Subject: [PATCH 034/135] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c9af5982f..49036d79f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,14 +4,11 @@ Follow the set of guidelines below to contribute to MyDollarBot!
## 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.
-
-Prerequistes required before starting this project:
+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 akilled@ncsu.edu.
If you like our project and would like to contribue otherwise, please note:
-1. This project is created as part of CSC510 Coursework at NC State for Fall'21.
-2. If you like to contribute, reach out to the maintainers before creating a branch, to confirm if we're still maintaining the repo.
-3. Have good understanding of Python Programming Language and Telegram Bots
+2. Reach out to the maintainers before creating a branch, to confirm if we're still maintaining the repo.
+3. Have the pre-requisites: Knowledge of Python and working with Telegram Bots.
## How can I Contribute -
From fd35e638a963528596c113a72c4560da701af180 Mon Sep 17 00:00:00 2001
From: Aditi Killedar
Date: Sun, 15 Oct 2023 11:03:40 -0400
Subject: [PATCH 035/135] Incorporated review recommendations
---
CODE_OF_CONDUCT.md | 2 ++
CONTRIBUTING.md | 21 +++++++++++++++++----
Developer_ReadMe.md | 6 +++---
Project Iteration 2 - New Features.pdf | Bin 361928 -> 0 bytes
README.md | 23 +++++++++++++++--------
5 files changed, 37 insertions(+), 15 deletions(-)
delete mode 100644 Project Iteration 2 - New Features.pdf
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index ff82f7f5a..35d2e5dcc 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -126,3 +126,5 @@ enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+
+We hope to see all contributions to our repository strictly adhereing to our code of conduct detailed above, to make contributions to DollarBot safe and welcoming.
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 49036d79f..565e15297 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,14 +1,27 @@
-# Contributing to MyDollarBot
+# Contributing to DollarBot
-Follow the set of guidelines below to contribute to MyDollarBot!
+Follow the set of guidelines below to contribute to DollarBot!
## Code of Conduct
+<<<<<<< HEAD
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 akilled@ncsu.edu.
If you like our project and would like to contribue otherwise, please note:
2. Reach out to the maintainers before creating a branch, to confirm if we're still maintaining the repo.
3. Have the pre-requisites: Knowledge of Python and working with Telegram Bots.
+=======
+This project and everyone participating in it is governed by the [Code of Conduct](https://github.com/aditikilledar/dollar_bot_SE23/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to upload this code. Please report unacceptable behavior to akilled@ncsu.edu.
+
+Note that this project is created as part of CSC510 Coursework at NC State for Fall'21.
+
+Prerequistes required before starting this project:
+- Working Python Knowledge
+- Some knowledge of Telegram Bots
+
+If you like our project and would like to contribue otherwise, please note:
+Before creating a branch, reach out to the maintainers to confirm if the reposityory is still being actively maintained.
+>>>>>>> b7845db (Incorporated review recommendations)
## How can I Contribute -
@@ -38,7 +51,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/aditikilledar/dollar_bot_SE23/issues) You can track the bugs from this. For the repository that has bug, create an issue and fill out [this template](https://github.com/atom/.github/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) or our in-repo template to give details of the bug.
* To identify the problem, give the issue a clear and informative term.
@@ -67,7 +80,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/aditikilledar/dollar_bot_SE23/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 or our in-repo enhancement template 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/Developer_ReadMe.md b/Developer_ReadMe.md
index f0f406f0f..bcbcb3cac 100644
--- a/Developer_ReadMe.md
+++ b/Developer_ReadMe.md
@@ -37,13 +37,13 @@ This project is licensed under the terms of the MIT license. Please check [Licen
## Code Documentation
-Checkout the [docs](https://github.com/sak007/MyDollarBot-BOTGo/tree/main/docs)
+Checkout the [docs](https://github.com/aditikilledar/dollar_bot_SE23/tree/main/docs) for a detailed explanation of each functionality and the expected working along with code file location.
## 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/aditikilledar/dollar_bot_SE23/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/aditikilledar/dollar_bot_SE23/issues/new/choose).
## Future RoadMap
diff --git a/Project Iteration 2 - New Features.pdf b/Project Iteration 2 - New Features.pdf
deleted file mode 100644
index fe04beb0637231396bd0c7f999108c12d2ca5127..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 361928
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