From f54850963c02e54418c23d221e3a860ab213049d Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Thu, 12 May 2022 09:39:57 +0200 Subject: [PATCH 1/3] small comment fix --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 0763021..3ba5689 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -464,7 +464,7 @@ def remove_share(message): """ user_id = int(message.from_user.id) - bot.send_message(chat_id=user_id, text='Type ISIN/Symbol/CompanyName of share to remove (if you are unsure do /portfolio, find your share and insert the value above amount):') + bot.send_message(chat_id=user_id, text='Type ISIN/Symbol/CompanyName of share to remove (if you are unsure do /shares, find your share and insert the value above amount):') bot.register_next_step_handler(message, remove_share_step) # wait for user to send ISIN, then call remove_share_step function From 321b2130544e98e97842d6b4e67fadedf92f784b Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Thu, 12 May 2022 13:30:56 +0200 Subject: [PATCH 2/3] added documentation --- .../api_handling/api_handler.html | 1571 ++++++++++++++ .../telegram_bot/api_handling/index.html | 65 + documentation/telegram_bot/bot.html | 1810 +++++++++++++++++ documentation/telegram_bot/bot_updates.html | 507 +++++ .../telegram_bot/helper_functions.html | 220 ++ documentation/telegram_bot/index.html | 85 + documentation/telegram_bot/news/index.html | 65 + .../telegram_bot/news/news_fetcher.html | 264 +++ documentation/telegram_bot/shares/index.html | 65 + .../telegram_bot/shares/share_fetcher.html | 384 ++++ 10 files changed, 5036 insertions(+) create mode 100644 documentation/telegram_bot/api_handling/api_handler.html create mode 100644 documentation/telegram_bot/api_handling/index.html create mode 100644 documentation/telegram_bot/bot.html create mode 100644 documentation/telegram_bot/bot_updates.html create mode 100644 documentation/telegram_bot/helper_functions.html create mode 100644 documentation/telegram_bot/index.html create mode 100644 documentation/telegram_bot/news/index.html create mode 100644 documentation/telegram_bot/news/news_fetcher.html create mode 100644 documentation/telegram_bot/shares/index.html create mode 100644 documentation/telegram_bot/shares/share_fetcher.html diff --git a/documentation/telegram_bot/api_handling/api_handler.html b/documentation/telegram_bot/api_handling/api_handler.html new file mode 100644 index 0000000..84fbb60 --- /dev/null +++ b/documentation/telegram_bot/api_handling/api_handler.html @@ -0,0 +1,1571 @@ + + + + + + +telegram_bot.api_handling.api_handler API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.api_handling.api_handler

+
+
+

script for communicating with webservice to get data from database

+
+ +Expand source code + +
"""
+script for communicating with webservice to get data from database
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "10.05.2022"
+__version__ = "1.0.2"
+__license__ = "None"
+
+# side-dependencies: none
+# Work in Progress
+
+import os
+import sys
+
+import requests as r
+from croniter import croniter  # used for checking cron formatting
+from dotenv import load_dotenv
+
+load_dotenv()  # loads environment vars
+
+
+# note: for more information about the api visit swagger documentation on https://gruppe1.testsites.info/api/docs#/
+
+class API_Handler:
+    """class for interacting with the api webservice
+    
+    Attributes:
+        db_adress (string): adress of the database
+        token (string): auth token for api
+
+    Methods:
+        reauthorize(email, password): set new credentials
+        get_user_keywords(user_id): gets the keywords of the user
+        set_keyword(user_id, keyword): sets the keyword of the user
+        delete_keyword(user_id, keyword): deletes the keyword of the user
+        get_user_shares(user_id): gets the shares of the user
+        set_share(user_id, symbol): sets the share of the user
+        delete_share(user_id, symbol): deletes the share of the user
+        get_user_transactions(user_id): gets the transactions of the user
+        set_transaction(user_id, transaction): sets the transaction of the user
+        get_user_portfolio(user_id): gets the portfolio of the user
+        set_portfolio(user_id, portfolio): sets the portfolio of the user
+        delete_portfolio(user_id, portfolio): deletes the portfolio of the user
+        set_cron_interval(user_id, interval): sets the cron interval of the user
+        set_admin(email, is_admin): sets the admin status of the user with the given email
+    """
+
+    def __init__(self, db_adress, email, password):
+        """initializes the API_Handler class
+
+        Args:
+            db_adress (string): adress of the database
+            email (string): email of the user
+            password (string): password of the user
+        """
+        self.db_adress = db_adress
+
+        payload = {'email': email, 'password': password}  # credentials for admin account that has all permissions to get and set data (in this case bot account)
+        with r.Session() as s:  # open session
+            p = s.post(self.db_adress + "/user/login", json=payload)  # login to webservice
+            if p.status_code == 200:
+                self.token = p.json()["data"]['token']  # store token for further authentication of requests
+            else:
+                print("Error: " + str(p.status_code) + " invalid credentials")
+                self.token = None
+
+    def reauthorize(self, email, password):  # can be used if token expired
+        """set new credentials
+
+        Args:
+            email (string): email of the user
+            password (string): password of the user
+
+        Returns:
+            token (string): new token or None if not 200
+
+        Raises:
+            None
+        """
+        payload = {'email': email, 'password': password}
+        with r.Session() as s:
+            p = s.post(self.db_adress + "/user/login", json=payload)
+            if p.status_code == 200:
+                self.token = p.json()["data"]['token']
+                return p.json()["data"]['token']
+            else:
+                self.token = None
+                return None
+
+    def get_user(self, user_id, max_retries=10):  # max retries are used recursively if the request fails
+        """gets the shares of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            json: json of user infos
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}  # authorization is bot_token:user_id (user_id is the id of the user you want to get data from)
+            req = s.get(self.db_adress + "/user", headers=headers)
+            if (req.status_code == 200):
+                return req.json()["data"]
+
+            else:
+                return self.get_user(user_id, max_retries - 1)  # if request fails try again recursively
+
+    def get_all_users(self, max_retries=10):
+        """gets all users
+
+        Args:
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of users
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token}
+            req = s.get(self.db_adress + "/users", headers=headers)
+            if (req.status_code == 200):
+                return req.json()["data"]
+
+            else:
+                return self.get_all_users(max_retries - 1)
+
+    def get_user_keywords(self, user_id, max_retries=10):
+        """gets the keywords of the user
+        
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of keywords
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        keywords = []
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/keywords", headers=headers)
+            if (req.status_code == 200):
+                keywords_json = req.json()["data"]
+                for keyword in keywords_json:  # keywords_json is a list of dictionaries
+                    keywords.append(keyword["keyword"])
+
+                return keywords  # will be empty if no keywords are set
+
+            else:
+                return self.get_user_keywords(user_id, max_retries - 1)
+
+    def set_keyword(self, user_id, keyword):
+        """sets the keyword of the user
+
+        Args:
+            user_id (int): id of the user
+            keyword (int): keyword of the user
+
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+            return req.status_code
+
+    def delete_keyword(self, user_id, keyword):
+        """deletes the keyword of the user
+
+        Args:
+            user_id (int): id of the user
+            keyword (string): keyword of the user
+
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+            return req.status_code
+
+    def get_user_shares(self, user_id, max_retries=10):
+        """gets the shares of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of shares
+
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/shares", headers=headers)
+            if (req.status_code == 200):
+                shares_json = req.json()["data"]
+                shares = []
+                for share in shares_json:
+                    shares.append(share["isin"])  # we only want the isin of the shares
+
+                return shares
+
+            else:
+                return self.get_user_shares(user_id, max_retries - 1)
+
+    def set_share(self, user_id, isin, comment):
+        """sets the share of the user
+
+        Args:
+            user_id (int): id of the user
+            isin (string): identifier of the share (standard is isin)
+            comment (string): comment of the share
+        
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.post(self.db_adress + "/share", json={"comment": comment, "isin": isin},
+                         headers=headers)  # set share by setting comment and isin, comment can be the real name of the share e.g. "Apple Inc."
+            return req.status_code
+
+    def delete_share(self, user_id, isin):
+        """deletes the share of the user
+
+        Args:
+            user_id (int): id of the user
+            isin (string): identifier of the share (standard is isin)
+        
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.delete(self.db_adress + "/share", json={"isin": str(isin)}, headers=headers)  # to delete a share only the isin is needed because it is unique, shares are not transactions!
+            return req.status_code
+
+    def get_user_transactions(self, user_id, max_retries=10):
+        """gets the transactions of the user
+        
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+        
+        Returns:
+            dict: dictionary of transactions
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/transactions", headers=headers)
+
+            if req.status_code == 200:
+                transactions_dict = req.json()["data"]
+                return transactions_dict
+            else:
+                return self.get_user_transactions(user_id, max_retries - 1)
+
+    def set_transaction(self, user_id, comment, isin, count, price, time):
+        """sets the transaction of the user
+
+        Args:
+            user_id (int): id of the user
+            comment (string): comment of the transaction
+            isin (string): isin of the transaction
+            count (float): count of the transaction
+            price (float): price of the transaction
+            time (string): time of the transaction formatted like e.g. "2011-10-05T14:48:00.000Z"
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            time = time[:-3] + "Z"  # remove last character and add Z to make it a valid date for db
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            transaction = {"comment": str(comment), "count": float(count), "isin": str(isin), "price": float(price),
+                           "time": str(time)}  # set transaction as JSON with all the attributes needed according to Swagger docs
+            req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers)
+            return req.status_code
+
+    def get_user_portfolio(self, user_id, max_retries=10):
+        """gets the portfolio of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+        
+        Returns:
+            dict: dictionary of portfolio
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/portfolio", headers=headers)  # get portfolio as JSON
+            if req.status_code == 200:
+                portfolio_dict = req.json()["data"]  # get the data of the JSON
+                return portfolio_dict
+            else:
+                return self.get_user_portfolio(user_id, max_retries - 1)
+
+    def set_cron_interval(self, user_id, cron_interval):
+        """sets the cron interval of the user
+
+        Args:
+            user_id (int): id of the user
+            cron_interval (String): Update interval in cron format => see https://crontab.guru/ for formatting
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        if not croniter.is_valid(cron_interval):  # check if cron_interval is in valid format
+            print("Error: Invalid cron format")
+            return -1  # return error code -1 if invalid cron format
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.put(self.db_adress + "/user/setCron", json={"cron": str(cron_interval)}, headers=headers)  # put not post (see swagger docs)
+            return req.status_code
+
+    def set_admin(self, email, is_admin):
+        """sets the admin of the user
+
+        Args:
+            email (string): email of the user
+            is_admin (bool): "true" if user should be Admin, "false" if not
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token}  # only bot token is needed, user is chosen by email
+            req = s.put(self.db_adress + "/user/setAdmin", json={"admin": is_admin, "email": str(email)}, headers=headers)
+            return req.status_code
+
+
+if __name__ == "__main__":  # editable, just for basic on the go testing of new functions
+
+    print("This is a module for the telegram bot. It is not intended to be run directly.")
+    handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD")))  # get creds from env
+    print(handler.token)
+    keywords = handler.get_user_keywords(user_id=1709356058)  # user_id here is currently mine (Linus)
+    print(keywords)
+    shares = handler.get_user_portfolio(user_id=1709356058)
+    print("set cron with status: " + str(handler.set_cron_interval(user_id=1709356058, cron_interval="0 0 * * *")))
+    user = handler.get_user(user_id=1709356058)
+    print(user)
+    all_users = handler.get_all_users()
+    admin_status = handler.set_admin("test@test.com", "true")
+    print(admin_status)
+    print(all_users)
+    print(shares)
+    sys.exit(1)
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class API_Handler +(db_adress, email, password) +
+
+

class for interacting with the api webservice

+

Attributes

+
+
db_adress : string
+
adress of the database
+
token : string
+
auth token for api
+
+

Methods

+

reauthorize(email, password): set new credentials +get_user_keywords(user_id): gets the keywords of the user +set_keyword(user_id, keyword): sets the keyword of the user +delete_keyword(user_id, keyword): deletes the keyword of the user +get_user_shares(user_id): gets the shares of the user +set_share(user_id, symbol): sets the share of the user +delete_share(user_id, symbol): deletes the share of the user +get_user_transactions(user_id): gets the transactions of the user +set_transaction(user_id, transaction): sets the transaction of the user +get_user_portfolio(user_id): gets the portfolio of the user +set_portfolio(user_id, portfolio): sets the portfolio of the user +delete_portfolio(user_id, portfolio): deletes the portfolio of the user +set_cron_interval(user_id, interval): sets the cron interval of the user +set_admin(email, is_admin): sets the admin status of the user with the given email

+

initializes the API_Handler class

+

Args

+
+
db_adress : string
+
adress of the database
+
email : string
+
email of the user
+
password : string
+
password of the user
+
+
+ +Expand source code + +
class API_Handler:
+    """class for interacting with the api webservice
+    
+    Attributes:
+        db_adress (string): adress of the database
+        token (string): auth token for api
+
+    Methods:
+        reauthorize(email, password): set new credentials
+        get_user_keywords(user_id): gets the keywords of the user
+        set_keyword(user_id, keyword): sets the keyword of the user
+        delete_keyword(user_id, keyword): deletes the keyword of the user
+        get_user_shares(user_id): gets the shares of the user
+        set_share(user_id, symbol): sets the share of the user
+        delete_share(user_id, symbol): deletes the share of the user
+        get_user_transactions(user_id): gets the transactions of the user
+        set_transaction(user_id, transaction): sets the transaction of the user
+        get_user_portfolio(user_id): gets the portfolio of the user
+        set_portfolio(user_id, portfolio): sets the portfolio of the user
+        delete_portfolio(user_id, portfolio): deletes the portfolio of the user
+        set_cron_interval(user_id, interval): sets the cron interval of the user
+        set_admin(email, is_admin): sets the admin status of the user with the given email
+    """
+
+    def __init__(self, db_adress, email, password):
+        """initializes the API_Handler class
+
+        Args:
+            db_adress (string): adress of the database
+            email (string): email of the user
+            password (string): password of the user
+        """
+        self.db_adress = db_adress
+
+        payload = {'email': email, 'password': password}  # credentials for admin account that has all permissions to get and set data (in this case bot account)
+        with r.Session() as s:  # open session
+            p = s.post(self.db_adress + "/user/login", json=payload)  # login to webservice
+            if p.status_code == 200:
+                self.token = p.json()["data"]['token']  # store token for further authentication of requests
+            else:
+                print("Error: " + str(p.status_code) + " invalid credentials")
+                self.token = None
+
+    def reauthorize(self, email, password):  # can be used if token expired
+        """set new credentials
+
+        Args:
+            email (string): email of the user
+            password (string): password of the user
+
+        Returns:
+            token (string): new token or None if not 200
+
+        Raises:
+            None
+        """
+        payload = {'email': email, 'password': password}
+        with r.Session() as s:
+            p = s.post(self.db_adress + "/user/login", json=payload)
+            if p.status_code == 200:
+                self.token = p.json()["data"]['token']
+                return p.json()["data"]['token']
+            else:
+                self.token = None
+                return None
+
+    def get_user(self, user_id, max_retries=10):  # max retries are used recursively if the request fails
+        """gets the shares of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            json: json of user infos
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}  # authorization is bot_token:user_id (user_id is the id of the user you want to get data from)
+            req = s.get(self.db_adress + "/user", headers=headers)
+            if (req.status_code == 200):
+                return req.json()["data"]
+
+            else:
+                return self.get_user(user_id, max_retries - 1)  # if request fails try again recursively
+
+    def get_all_users(self, max_retries=10):
+        """gets all users
+
+        Args:
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of users
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token}
+            req = s.get(self.db_adress + "/users", headers=headers)
+            if (req.status_code == 200):
+                return req.json()["data"]
+
+            else:
+                return self.get_all_users(max_retries - 1)
+
+    def get_user_keywords(self, user_id, max_retries=10):
+        """gets the keywords of the user
+        
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of keywords
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        keywords = []
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/keywords", headers=headers)
+            if (req.status_code == 200):
+                keywords_json = req.json()["data"]
+                for keyword in keywords_json:  # keywords_json is a list of dictionaries
+                    keywords.append(keyword["keyword"])
+
+                return keywords  # will be empty if no keywords are set
+
+            else:
+                return self.get_user_keywords(user_id, max_retries - 1)
+
+    def set_keyword(self, user_id, keyword):
+        """sets the keyword of the user
+
+        Args:
+            user_id (int): id of the user
+            keyword (int): keyword of the user
+
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+            return req.status_code
+
+    def delete_keyword(self, user_id, keyword):
+        """deletes the keyword of the user
+
+        Args:
+            user_id (int): id of the user
+            keyword (string): keyword of the user
+
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+            return req.status_code
+
+    def get_user_shares(self, user_id, max_retries=10):
+        """gets the shares of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+
+        Returns:
+            list: list of shares
+
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/shares", headers=headers)
+            if (req.status_code == 200):
+                shares_json = req.json()["data"]
+                shares = []
+                for share in shares_json:
+                    shares.append(share["isin"])  # we only want the isin of the shares
+
+                return shares
+
+            else:
+                return self.get_user_shares(user_id, max_retries - 1)
+
+    def set_share(self, user_id, isin, comment):
+        """sets the share of the user
+
+        Args:
+            user_id (int): id of the user
+            isin (string): identifier of the share (standard is isin)
+            comment (string): comment of the share
+        
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.post(self.db_adress + "/share", json={"comment": comment, "isin": isin},
+                         headers=headers)  # set share by setting comment and isin, comment can be the real name of the share e.g. "Apple Inc."
+            return req.status_code
+
+    def delete_share(self, user_id, isin):
+        """deletes the share of the user
+
+        Args:
+            user_id (int): id of the user
+            isin (string): identifier of the share (standard is isin)
+        
+        Returns:
+            int: status code
+
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.delete(self.db_adress + "/share", json={"isin": str(isin)}, headers=headers)  # to delete a share only the isin is needed because it is unique, shares are not transactions!
+            return req.status_code
+
+    def get_user_transactions(self, user_id, max_retries=10):
+        """gets the transactions of the user
+        
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+        
+        Returns:
+            dict: dictionary of transactions
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/transactions", headers=headers)
+
+            if req.status_code == 200:
+                transactions_dict = req.json()["data"]
+                return transactions_dict
+            else:
+                return self.get_user_transactions(user_id, max_retries - 1)
+
+    def set_transaction(self, user_id, comment, isin, count, price, time):
+        """sets the transaction of the user
+
+        Args:
+            user_id (int): id of the user
+            comment (string): comment of the transaction
+            isin (string): isin of the transaction
+            count (float): count of the transaction
+            price (float): price of the transaction
+            time (string): time of the transaction formatted like e.g. "2011-10-05T14:48:00.000Z"
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            time = time[:-3] + "Z"  # remove last character and add Z to make it a valid date for db
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            transaction = {"comment": str(comment), "count": float(count), "isin": str(isin), "price": float(price),
+                           "time": str(time)}  # set transaction as JSON with all the attributes needed according to Swagger docs
+            req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers)
+            return req.status_code
+
+    def get_user_portfolio(self, user_id, max_retries=10):
+        """gets the portfolio of the user
+
+        Args:
+            user_id (int): id of the user
+            max_retries (int): max retries for the request
+        
+        Returns:
+            dict: dictionary of portfolio
+        
+        Raises:
+            None
+        """
+        if max_retries <= 0:
+            return None
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.get(self.db_adress + "/portfolio", headers=headers)  # get portfolio as JSON
+            if req.status_code == 200:
+                portfolio_dict = req.json()["data"]  # get the data of the JSON
+                return portfolio_dict
+            else:
+                return self.get_user_portfolio(user_id, max_retries - 1)
+
+    def set_cron_interval(self, user_id, cron_interval):
+        """sets the cron interval of the user
+
+        Args:
+            user_id (int): id of the user
+            cron_interval (String): Update interval in cron format => see https://crontab.guru/ for formatting
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        if not croniter.is_valid(cron_interval):  # check if cron_interval is in valid format
+            print("Error: Invalid cron format")
+            return -1  # return error code -1 if invalid cron format
+
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+            req = s.put(self.db_adress + "/user/setCron", json={"cron": str(cron_interval)}, headers=headers)  # put not post (see swagger docs)
+            return req.status_code
+
+    def set_admin(self, email, is_admin):
+        """sets the admin of the user
+
+        Args:
+            email (string): email of the user
+            is_admin (bool): "true" if user should be Admin, "false" if not
+        
+        Returns:
+            int: status code
+        
+        Raises:
+            None
+        """
+        with r.Session() as s:
+            headers = {'Authorization': 'Bearer ' + self.token}  # only bot token is needed, user is chosen by email
+            req = s.put(self.db_adress + "/user/setAdmin", json={"admin": is_admin, "email": str(email)}, headers=headers)
+            return req.status_code
+
+

Methods

+
+
+def delete_keyword(self, user_id, keyword) +
+
+

deletes the keyword of the user

+

Args

+
+
user_id : int
+
id of the user
+
keyword : string
+
keyword of the user
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def delete_keyword(self, user_id, keyword):
+    """deletes the keyword of the user
+
+    Args:
+        user_id (int): id of the user
+        keyword (string): keyword of the user
+
+    Returns:
+        int: status code
+
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+        return req.status_code
+
+
+
+def delete_share(self, user_id, isin) +
+
+

deletes the share of the user

+

Args

+
+
user_id : int
+
id of the user
+
isin : string
+
identifier of the share (standard is isin)
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def delete_share(self, user_id, isin):
+    """deletes the share of the user
+
+    Args:
+        user_id (int): id of the user
+        isin (string): identifier of the share (standard is isin)
+    
+    Returns:
+        int: status code
+
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.delete(self.db_adress + "/share", json={"isin": str(isin)}, headers=headers)  # to delete a share only the isin is needed because it is unique, shares are not transactions!
+        return req.status_code
+
+
+
+def get_all_users(self, max_retries=10) +
+
+

gets all users

+

Args

+
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
list
+
list of users
+
+

Raises

+

None

+
+ +Expand source code + +
def get_all_users(self, max_retries=10):
+    """gets all users
+
+    Args:
+        max_retries (int): max retries for the request
+
+    Returns:
+        list: list of users
+    
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token}
+        req = s.get(self.db_adress + "/users", headers=headers)
+        if (req.status_code == 200):
+            return req.json()["data"]
+
+        else:
+            return self.get_all_users(max_retries - 1)
+
+
+
+def get_user(self, user_id, max_retries=10) +
+
+

gets the shares of the user

+

Args

+
+
user_id : int
+
id of the user
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
json
+
json of user infos
+
+

Raises

+

None

+
+ +Expand source code + +
def get_user(self, user_id, max_retries=10):  # max retries are used recursively if the request fails
+    """gets the shares of the user
+
+    Args:
+        user_id (int): id of the user
+        max_retries (int): max retries for the request
+
+    Returns:
+        json: json of user infos
+    
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}  # authorization is bot_token:user_id (user_id is the id of the user you want to get data from)
+        req = s.get(self.db_adress + "/user", headers=headers)
+        if (req.status_code == 200):
+            return req.json()["data"]
+
+        else:
+            return self.get_user(user_id, max_retries - 1)  # if request fails try again recursively
+
+
+
+def get_user_keywords(self, user_id, max_retries=10) +
+
+

gets the keywords of the user

+

Args

+
+
user_id : int
+
id of the user
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
list
+
list of keywords
+
+

Raises

+

None

+
+ +Expand source code + +
def get_user_keywords(self, user_id, max_retries=10):
+    """gets the keywords of the user
+    
+    Args:
+        user_id (int): id of the user
+        max_retries (int): max retries for the request
+
+    Returns:
+        list: list of keywords
+    
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    keywords = []
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.get(self.db_adress + "/keywords", headers=headers)
+        if (req.status_code == 200):
+            keywords_json = req.json()["data"]
+            for keyword in keywords_json:  # keywords_json is a list of dictionaries
+                keywords.append(keyword["keyword"])
+
+            return keywords  # will be empty if no keywords are set
+
+        else:
+            return self.get_user_keywords(user_id, max_retries - 1)
+
+
+
+def get_user_portfolio(self, user_id, max_retries=10) +
+
+

gets the portfolio of the user

+

Args

+
+
user_id : int
+
id of the user
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
dict
+
dictionary of portfolio
+
+

Raises

+

None

+
+ +Expand source code + +
def get_user_portfolio(self, user_id, max_retries=10):
+    """gets the portfolio of the user
+
+    Args:
+        user_id (int): id of the user
+        max_retries (int): max retries for the request
+    
+    Returns:
+        dict: dictionary of portfolio
+    
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.get(self.db_adress + "/portfolio", headers=headers)  # get portfolio as JSON
+        if req.status_code == 200:
+            portfolio_dict = req.json()["data"]  # get the data of the JSON
+            return portfolio_dict
+        else:
+            return self.get_user_portfolio(user_id, max_retries - 1)
+
+
+
+def get_user_shares(self, user_id, max_retries=10) +
+
+

gets the shares of the user

+

Args

+
+
user_id : int
+
id of the user
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
list
+
list of shares
+
+

Raises

+

None

+
+ +Expand source code + +
def get_user_shares(self, user_id, max_retries=10):
+    """gets the shares of the user
+
+    Args:
+        user_id (int): id of the user
+        max_retries (int): max retries for the request
+
+    Returns:
+        list: list of shares
+
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.get(self.db_adress + "/shares", headers=headers)
+        if (req.status_code == 200):
+            shares_json = req.json()["data"]
+            shares = []
+            for share in shares_json:
+                shares.append(share["isin"])  # we only want the isin of the shares
+
+            return shares
+
+        else:
+            return self.get_user_shares(user_id, max_retries - 1)
+
+
+
+def get_user_transactions(self, user_id, max_retries=10) +
+
+

gets the transactions of the user

+

Args

+
+
user_id : int
+
id of the user
+
max_retries : int
+
max retries for the request
+
+

Returns

+
+
dict
+
dictionary of transactions
+
+

Raises

+

None

+
+ +Expand source code + +
def get_user_transactions(self, user_id, max_retries=10):
+    """gets the transactions of the user
+    
+    Args:
+        user_id (int): id of the user
+        max_retries (int): max retries for the request
+    
+    Returns:
+        dict: dictionary of transactions
+    
+    Raises:
+        None
+    """
+    if max_retries <= 0:
+        return None
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.get(self.db_adress + "/transactions", headers=headers)
+
+        if req.status_code == 200:
+            transactions_dict = req.json()["data"]
+            return transactions_dict
+        else:
+            return self.get_user_transactions(user_id, max_retries - 1)
+
+
+
+def reauthorize(self, email, password) +
+
+

set new credentials

+

Args

+
+
email : string
+
email of the user
+
password : string
+
password of the user
+
+

Returns

+

token (string): new token or None if not 200

+

Raises

+

None

+
+ +Expand source code + +
def reauthorize(self, email, password):  # can be used if token expired
+    """set new credentials
+
+    Args:
+        email (string): email of the user
+        password (string): password of the user
+
+    Returns:
+        token (string): new token or None if not 200
+
+    Raises:
+        None
+    """
+    payload = {'email': email, 'password': password}
+    with r.Session() as s:
+        p = s.post(self.db_adress + "/user/login", json=payload)
+        if p.status_code == 200:
+            self.token = p.json()["data"]['token']
+            return p.json()["data"]['token']
+        else:
+            self.token = None
+            return None
+
+
+
+def set_admin(self, email, is_admin) +
+
+

sets the admin of the user

+

Args

+
+
email : string
+
email of the user
+
is_admin : bool
+
"true" if user should be Admin, "false" if not
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def set_admin(self, email, is_admin):
+    """sets the admin of the user
+
+    Args:
+        email (string): email of the user
+        is_admin (bool): "true" if user should be Admin, "false" if not
+    
+    Returns:
+        int: status code
+    
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token}  # only bot token is needed, user is chosen by email
+        req = s.put(self.db_adress + "/user/setAdmin", json={"admin": is_admin, "email": str(email)}, headers=headers)
+        return req.status_code
+
+
+
+def set_cron_interval(self, user_id, cron_interval) +
+
+

sets the cron interval of the user

+

Args

+
+
user_id : int
+
id of the user
+
cron_interval : String
+
Update interval in cron format => see https://crontab.guru/ for formatting
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def set_cron_interval(self, user_id, cron_interval):
+    """sets the cron interval of the user
+
+    Args:
+        user_id (int): id of the user
+        cron_interval (String): Update interval in cron format => see https://crontab.guru/ for formatting
+    
+    Returns:
+        int: status code
+    
+    Raises:
+        None
+    """
+    if not croniter.is_valid(cron_interval):  # check if cron_interval is in valid format
+        print("Error: Invalid cron format")
+        return -1  # return error code -1 if invalid cron format
+
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.put(self.db_adress + "/user/setCron", json={"cron": str(cron_interval)}, headers=headers)  # put not post (see swagger docs)
+        return req.status_code
+
+
+
+def set_keyword(self, user_id, keyword) +
+
+

sets the keyword of the user

+

Args

+
+
user_id : int
+
id of the user
+
keyword : int
+
keyword of the user
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def set_keyword(self, user_id, keyword):
+    """sets the keyword of the user
+
+    Args:
+        user_id (int): id of the user
+        keyword (int): keyword of the user
+
+    Returns:
+        int: status code
+
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers)
+
+        return req.status_code
+
+
+
+def set_share(self, user_id, isin, comment) +
+
+

sets the share of the user

+

Args

+
+
user_id : int
+
id of the user
+
isin : string
+
identifier of the share (standard is isin)
+
comment : string
+
comment of the share
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def set_share(self, user_id, isin, comment):
+    """sets the share of the user
+
+    Args:
+        user_id (int): id of the user
+        isin (string): identifier of the share (standard is isin)
+        comment (string): comment of the share
+    
+    Returns:
+        int: status code
+
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        req = s.post(self.db_adress + "/share", json={"comment": comment, "isin": isin},
+                     headers=headers)  # set share by setting comment and isin, comment can be the real name of the share e.g. "Apple Inc."
+        return req.status_code
+
+
+
+def set_transaction(self, user_id, comment, isin, count, price, time) +
+
+

sets the transaction of the user

+

Args

+
+
user_id : int
+
id of the user
+
comment : string
+
comment of the transaction
+
isin : string
+
isin of the transaction
+
count : float
+
count of the transaction
+
price : float
+
price of the transaction
+
time : string
+
time of the transaction formatted like e.g. "2011-10-05T14:48:00.000Z"
+
+

Returns

+
+
int
+
status code
+
+

Raises

+

None

+
+ +Expand source code + +
def set_transaction(self, user_id, comment, isin, count, price, time):
+    """sets the transaction of the user
+
+    Args:
+        user_id (int): id of the user
+        comment (string): comment of the transaction
+        isin (string): isin of the transaction
+        count (float): count of the transaction
+        price (float): price of the transaction
+        time (string): time of the transaction formatted like e.g. "2011-10-05T14:48:00.000Z"
+    
+    Returns:
+        int: status code
+    
+    Raises:
+        None
+    """
+    with r.Session() as s:
+        time = time[:-3] + "Z"  # remove last character and add Z to make it a valid date for db
+        headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)}
+        transaction = {"comment": str(comment), "count": float(count), "isin": str(isin), "price": float(price),
+                       "time": str(time)}  # set transaction as JSON with all the attributes needed according to Swagger docs
+        req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers)
+        return req.status_code
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/api_handling/index.html b/documentation/telegram_bot/api_handling/index.html new file mode 100644 index 0000000..a1e025c --- /dev/null +++ b/documentation/telegram_bot/api_handling/index.html @@ -0,0 +1,65 @@ + + + + + + +telegram_bot.api_handling API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.api_handling

+
+
+
+
+

Sub-modules

+
+
telegram_bot.api_handling.api_handler
+
+

script for communicating with webservice to get data from database

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/bot.html b/documentation/telegram_bot/bot.html new file mode 100644 index 0000000..2307945 --- /dev/null +++ b/documentation/telegram_bot/bot.html @@ -0,0 +1,1810 @@ + + + + + + +telegram_bot.bot API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.bot

+
+
+

script for telegram bot and its functions

+
+ +Expand source code + +
"""
+script for telegram bot and its functions
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "10.05.2022"
+__version__ = "1.2.3"
+__license__ = "None"
+
+# side-dependencies: none
+# Work in Progress
+# text bot at t.me/projektaktienbot
+# API Documentation https://core.telegram.org/bots/api
+# Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started
+
+import datetime as dt
+import logging
+import os
+import re
+import sys
+
+import telebot
+from dotenv import load_dotenv
+from telebot import types
+
+import telegram_bot.helper_functions as hf
+import telegram_bot.news.news_fetcher as news
+import telegram_bot.shares.share_fetcher as share_fetcher
+from telegram_bot.api_handling.api_handler import API_Handler
+
+load_dotenv(dotenv_path='.env')  # load environment variables
+
+bot_version = "2.0.1"  # version of bot
+
+# create api handler
+api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD")))  # get creds from env vars.
+print("Webserver Token: " + str(api_handler.token))
+
+bot = telebot.TeleBot(os.getenv('BOT_API_KEY'))
+
+
+@bot.message_handler(commands=['start', 'Start'])
+def send_start(message):
+    """ Sending welcome message to new user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/start'
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message, "Welcome to this share bot project. \
+                 \nType /help to get information on what this bot can do. \
+                 \nAlso see https://gruppe1.testsites.info \
+                 to start configuring your bot")
+
+
+@bot.message_handler(commands=['version', 'Version'])
+def send_version(message):
+    """ Sending programm version
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/version'
+
+    :raises: none
+
+    :rtype:none
+    """
+    bot.reply_to(message, "the current bot version is " + bot_version)
+
+
+@bot.message_handler(commands=['help', 'Help'])  # /help -> sending all functions
+def send_help(message):
+    """ Send all functions
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/help'
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message,
+                 "/id or /auth get your user id\n/update get updates on your shares.\n/shares get update on interesting shares\n/setAdmin set admin rights of user (ADMIN)\n/users see all users. (ADMIN)\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/transactions get all transactions\n/newtransaction create new transaction\n/share get price of specific share\n/portfolio see own portfolio\n/removeshare removes share from portfolio\n/interval get update interval\n/setinterval set update interval\n For further details see https://gruppe1.testsites.info")
+
+
+@bot.message_handler(commands=['users', 'Users'])  # /users -> sending all users
+def send_all_users(message):
+    """ Send all users, only possible for admins
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/users'
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+    if (user_data["admin"] == False):  # check if user has admin rights
+        bot.reply_to(message, "You have to be an admin to use this command")
+        return
+
+    user_list = api_handler.get_all_users()
+    user_count = len(user_list)
+    bot.send_message(chat_id=user_id, text="There are " + str(user_count) + " users in the database:")
+
+    for user in user_list:
+        username = user['username']
+        email = user['email']
+        id = user['telegram_user_id']
+        cron = user['cron']
+        admin = user['admin']
+
+        bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {id}\nCron: {cron}\nAdmin: {admin}')  # format user data into readable message text
+
+
+@bot.message_handler(commands=['setAdmin', 'SetAdmin', 'setadmin', 'Setadmin'])  # set admin rights to user TBD: not working!!
+def set_admin(message):
+    """ Set admin rights to user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/setAdmin'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+
+    if (user_data["admin"] == False):  # check if user has admin rights
+        bot.reply_to(message, "You have to be an admin to use this command")
+        return
+
+    bot.send_message(chat_id=user_id, text='send email and true if this account should have admin rights, else false\n in format: <email>,<is_admin>')  # request email and admin rights to change to
+    bot.register_next_step_handler(message, set_admin_step)
+
+
+def set_admin_step(message):
+    str_message = str(message.text)
+    args_message = str_message.split(',')  # split message into email and admin rights
+
+    if len(args_message) != 2:  # make sure 2 args (email,is_admin) are given
+
+        bot.reply_to(message, "exactly 2 arguments (<email>,<is_admin>) required, try again")
+        return
+
+    email = args_message[0]
+    is_admin = False  # default: False
+
+    if args_message[1].lower() == "true":  # if user types true, set is_admin to true
+        is_admin = True
+
+    status = api_handler.set_admin(email, is_admin)  # set admin in db
+
+    if (status == 200):
+        bot.reply_to(message, "Admin rights set")
+
+    else:
+        bot.reply_to(message, f"Admin rights could not be set ({status})")
+
+
+@bot.message_handler(commands=['me', 'Me'])  # /me -> sending user info
+def send_user(message):
+    """ Send user data
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/me'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+    if not user_data or user_data == None:  # true if user is not registered
+        bot.reply_to(message, "This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info")
+        return
+    username = user_data['username']
+    email = user_data['email']
+    user_id = user_data['telegram_user_id']
+    cron = user_data['cron']
+    admin = user_data['admin']
+    bot.reply_to(message, f'Username: {username}\nEmail: {email}\nID: {user_id}\nCron: {cron}\nAdmin: {admin}')  # format user data into readable message text
+
+
+@bot.message_handler(commands=['id', 'auth', 'Id', 'Auth'])  # /auth or /id -> Authentication with user_id over web tool
+def send_id(message):
+    """ Send user id for authentication with browser
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/id' or '/auth'
+
+    :raises: none
+
+    :rtype: none
+    """
+    answer = 'Your ID/Authentication Code is: [' + str(message.from_user.id) + ']. Enter this code in the settings on https://gruppe1.testsites.info to get updates on your shares.'
+    bot.reply_to(message, answer)
+
+
+# function that can be used to ensure that the bot is online and running
+@bot.message_handler(commands=['status', 'Status'])
+def send_status(message):
+    """ Sends status to user
+    :type message: message object bot
+    :param message: message that was reacted to, if no other command handler gets called
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message, "bot is running")
+
+
+@bot.message_handler(commands=['update', 'Update'])  # /update -> update shares
+def update_for_user(message):
+    p_user_id = int(message.from_user.id)
+    p_my_handler = api_handler
+
+    share_symbols = []
+    share_amounts = []
+
+    my_portfolio = p_my_handler.get_user_portfolio(p_user_id)
+
+    for element in my_portfolio:
+        if element["count"] != '' and element["isin"] != '':
+            print(element["count"], element["isin"])
+            share_symbols.append(element["isin"])
+            share_amounts.append(element["count"])
+
+    my_user = p_my_handler.get_user(p_user_id)
+    send_to_user("Hello %s this is your share update:" % str(my_user["username"]), pUser_id=p_user_id)
+
+    if len(share_symbols) != 0:
+        for i in range(len(share_symbols)):
+            my_price = share_fetcher.get_share_price_no_currency(share_symbols[i])
+            my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {share_amounts[i]}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR'
+            bot.send_message(chat_id=p_user_id, text=my_update_message, parse_mode="MARKDOWNV2")
+    else:
+        send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id)
+
+
+def send_to_user(pText, pUser_id):
+    """ Send message to user
+    :type pText: string
+    :param pText: Text to send to user
+
+    :type pUser_id: int
+    :param pUser_id: user to send to. per default me (Florian Kellermann)
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.send_message(chat_id=pUser_id, text=pText)
+
+
+@bot.message_handler(commands=['share', 'Share'])  # /share -> get share price
+def send_share_update(message):
+    """ Send price of a specific share
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/share'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+
+    bot.send_message(chat_id=user_id, text='Send Symbol/ISIN of share or name of company:')
+    bot.register_next_step_handler(message, send_share_price)
+
+
+def send_share_price(message):
+    str_share_price = share_fetcher.get_share_information_markdown(str(message.text))
+    bot.reply_to(message, str_share_price, parse_mode="MARKDOWNV2")
+
+
+@bot.message_handler(commands=['allnews', 'Allnews'])  # /allnews -> get all news
+def send_all_news(message):
+    """ Get news for keywords of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/allnews'
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword')
+        return
+
+    keywords_search = ' OR '.join(keywords)  # concat all keywords with OR -> NewsAPI can understand OR, AND, NOT etc.
+    now = dt.datetime.now().date()  # get current date
+    from_date = now - dt.timedelta(days=7)  # get date 7 days ago -> limit age of news to 7 days old max
+    from_date_formatted = dt.datetime.strftime(from_date, '%Y-%m-%d')
+    news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"]  # array of JSON article objects
+
+    if news_list:  # true if news_list is not empty
+        for article in news_list:
+            formatted_article = news.format_article(article)
+            bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWNV2")  # Markdown allows to write bold text with * etc.
+    else:
+        bot.send_message(chat_id=user_id, text='No news found for your keywords.')
+
+
+@bot.message_handler(commands=['news', 'News'])  # /news -> get news for specific keyword
+def send_news(message):
+    """ Get news for keywords of user
+
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/news'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword')
+        return
+
+    if keywords:
+        for keyword in keywords:
+            top_news = news.get_top_news_by_keyword(keyword)["articles"]
+            if top_news == None:  # true if request to NewsAPI failed
+                bot.send_message(chat_id=user_id, text='News Server did not respond correctly. Try again later.')
+
+            if not top_news:  # true if no news found for keyword (empty list)
+                keyword = hf.make_markdown_proof(keyword)
+                bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWNV2")
+
+            else:
+                keyword = hf.make_markdown_proof(keyword)
+                formatted_article = news.format_article(top_news[0])  # only format and send most popular news
+                bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWNV2")  # do not use v2 because of bugs related t "." in links
+
+
+@bot.message_handler(commands=['addkeyword', 'Addkeyword'])  # /addkeyword -> add keyword to user
+def add_keyword(message):
+    """ Add keyword to user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/addkeyword'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type keyword to add:')
+    bot.register_next_step_handler(message, store_keyword)  # wait for user to send keyword, then call store_keyword function
+
+
+def store_keyword(message):
+    user_id = int(message.from_user.id)
+    keyword = str(message.text).lower()  # lower to ensure Bitcoin and bitcoin is not stored as individual keywords
+    status = api_handler.set_keyword(user_id, keyword)  # set keyword in database
+    if status == 200:  # statuscode 200 means keyword was added successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.')  # duplicate keywords are denied by Database, so no need to check for that here
+    else:
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info (statuscode {status})')
+
+
+@bot.message_handler(commands=['removekeyword', 'Removekeyword'])  # /removekeyword -> remove keyword from user
+def remove_keyword(message):
+    """ Remove keyword from user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/removekeyword'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type keyword to remove:')
+    bot.register_next_step_handler(message, remove_keyword_step)  # wait for user to send keyword to remove, then call remove_keyword_step function
+
+
+def remove_keyword_step(message):
+    user_id = int(message.from_user.id)
+    keyword = str(message.text).lower()
+    status = api_handler.delete_keyword(user_id, keyword)
+    if status == 200:  # statuscode 200 means keyword was removed successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.')  # checking if keyword to remove is in database are handled in database, not here
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed deleting keyword "{keyword}". (statuscode {status})')
+
+
+@bot.message_handler(commands=['keywords', 'Keywords'])  # /keywords -> get keywords of user
+def send_keywords(message):
+    """ Send keywords of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/keywords'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='No keywords set for this account. Add keywords by using /addkeyword')
+        return
+
+    else:  # send keyword list
+        keywords_str = ', '.join(keywords)
+        keywords_str = hf.make_markdown_proof(keywords_str)
+
+        text = f'Your keywords are: _{keywords_str}_'
+        bot.send_message(chat_id=user_id, text=text, parse_mode="MARKDOWNV2")
+
+
+@bot.message_handler(commands=['portfolio', 'Portfolio'])
+def send_portfolio(message):
+    """ Send portfolio of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/portfolio'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    portfolio = api_handler.get_user_portfolio(user_id)  # get portfolio of user as json
+    if portfolio == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+    if not portfolio:  # true if user is registered but does not have any stocks in portfolio
+        bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.')
+        return
+    else:  # send portfolio
+        for stock in portfolio:
+            comment = hf.make_markdown_proof(str(stock["comment"]))  # comment may be written name of stock, comment is made by user when adding an stock to portfolio
+            count = hf.make_markdown_proof("{:.2f}".format(float(stock["count"])))  # round count to 2 decimal places
+            isin = hf.make_markdown_proof(str(stock["isin"]))
+            worth = hf.make_markdown_proof("{:.2f}".format(float(stock["current_price"]) * float(stock["count"])))  # round current_price to 2 decimal places
+            bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWNV2")  # formatted message in markdown
+
+
+@bot.message_handler(commands=['removeshare', 'Removeshare'])
+def remove_share(message):
+    """ Remove share from portfolio
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/removeshare'
+    
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+
+    bot.send_message(chat_id=user_id, text='Type ISIN/Symbol/CompanyName of share to remove (if you are unsure do /shares, find your share and insert the value above amount):')
+    bot.register_next_step_handler(message, remove_share_step)  # wait for user to send ISIN, then call remove_share_step function
+
+
+def remove_share_step(message):
+    user_id = int(message.from_user.id)
+    isin = str(message.text)
+
+    status = api_handler.delete_share(int(user_id), str(isin))  # remove share from portfolio
+
+    if status == 200:  # statuscode 200 means share was removed successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Share "{isin}" removed.')  # checking if share to remove is in database are handled in database, not here
+
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed deleting share "{isin}". (statuscode {status})\nMake sure that the share is in your portfolio and written exactly like there.')
+
+
+@bot.message_handler(commands=['newtransaction', 'Newtransaction'])  # tbd not working rn may be deleted in future
+def set_new_transaction(message):
+    """ Set new transaction for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/newtransaction'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id,
+                     text='Type "<name of stock>,<isin/name/symbol>,<amount>,<price_per_stock_usd>" (time of transaction will be set to now, negative amount is selling, positive is buying):')
+    bot.register_next_step_handler(message, set_new_transaction_step)
+
+
+def set_new_transaction_step(message):
+    user_id = int(message.from_user.id)
+
+    if not re.match(r"[A-Za-z0-9 ]+,[A-Za-z0-9]+,(-)?[0-9]+(.[0-9]+)?,[0-9]+(.[0-9]+)?", message.text):
+        bot.send_message(chat_id=user_id, text='Invalid format \n(e.g. Apple,US0378331005,53.2,120.4).\n Try again with /newtransaction.')
+        return
+
+    transaction_data = str(message.text).split(',')
+    desc = str(transaction_data[0])
+    isin = str(transaction_data[1])
+    amount = float(transaction_data[2])
+    price = float(transaction_data[3])
+    time = dt.datetime.now().isoformat()
+    print("\n\n\n\n\n")
+    print(f"{isin},{amount},{price},{time}")
+    status = api_handler.set_transaction(user_id, desc, isin, amount, price, time)
+
+    if status == 200:
+        bot.send_message(chat_id=user_id, text='Transaction succesfully added.')
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})')
+
+
+@bot.message_handler(commands=['interval', 'Interval'])
+def send_interval(message):
+    """ send interval for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/interval'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)  # get cron interval of user (stored in user data)
+    if user_data == None:  # true if user is not registered in DB
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval')
+        return
+    else:  # send interval
+        interval = str(user_data['cron'])  # get cron from user data
+        if interval == 'None':  # true if user has no cron set
+            bot.send_message(chat_id=user_id, text='You do not have an interval set. Set one with /setinterval')
+            return
+        formatted_interval = str(interval).replace(' ', '_')  # replace spaces with underscores to add to url of crontab.guru
+        bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})')
+
+
+@bot.message_handler(commands=['transactions', 'Transactions'])
+def send_transactions(message):
+    """ send transactions for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/transactions'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    transactions = api_handler.get_user_transactions(user_id)  # get transactions of user
+
+    if transactions == None:  # true if user does not exist
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not transactions:  # true if user has no transactions
+        bot.send_message(chat_id=user_id, text='You do not have any transactions.')
+        return
+
+    else:
+
+        for transaction in transactions:
+            comment = hf.make_markdown_proof(transaction['comment']) or "\(no desc\)"  # if comment is empty, make it "no desc"
+            isin = hf.make_markdown_proof(transaction['isin'])
+            amount = hf.make_markdown_proof(transaction['count'])
+            price = hf.make_markdown_proof(transaction['price'])
+            time = hf.make_markdown_proof(transaction['time'])
+
+            bot.send_message(chat_id=user_id, text=f'_{comment}_\n{isin}\namount: {amount}\nprice: {price}\ntime: {time}', parse_mode="MARKDOWNV2")
+
+
+@bot.message_handler(commands=['shares', 'Shares'])
+def send_shares(message):
+    """ send shares for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/shares'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    shares = api_handler.get_user_shares(user_id)  # get shares of user
+
+    if shares == None:  # true if user does not exist
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+    elif not shares:  # true if user has no shares
+        bot.send_message(chat_id=user_id, text='You do not have any shares. Add shares on https://gruppe1.testsites.info')
+    else:
+        for element in shares:
+            bot.send_message(chat_id=user_id, text=share_fetcher.get_share_information_markdown(element), parse_mode="MARKDOWNV2")
+
+
+@bot.message_handler(commands=['setinterval', 'Setinterval'])
+def set_new_interval(message):
+    """ Set new interval for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/setinterval'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type interval in cron format:\n(https://crontab.guru/)')
+    bot.register_next_step_handler(message, set_new_interval_step)  # executes function when user sends message
+
+
+def set_new_interval_step(message):
+    user_id = int(message.from_user.id)
+    interval = str(message.text)
+    status = api_handler.set_cron_interval(user_id, interval)  # send cron to db
+
+    if status == 200:
+        bot.send_message(chat_id=user_id, text='Interval succesfully set.')
+        return
+
+    if status == -1:  # only -1 when interval is invalid, not a real statuscode, but used from api_handler.set_cron_interval to tell the crontab has the wrong format
+        bot.send_message(chat_id=user_id, text='Invalid interval format. Try again with\n /setinterval.')
+        return
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed setting interval. (statuscode {status})')
+
+
+@bot.message_handler(func=lambda message: True)  # Returning that command is unknown for any other statement
+def echo_all(message):
+    """ Tell that command is not known if it is no known command
+    :type message: message object bot
+    :param message: message that was reacted to, if no other command handler gets called
+
+    :raises: none
+
+    :rtype: none
+    """
+    answer = 'Do not know this command or text: ' + message.text
+    bot.reply_to(message, answer)
+
+
+telebot.logger.setLevel(logging.DEBUG)
+
+
+@bot.inline_handler(lambda query: query.query == 'text')  # inline prints for debugging
+def query_text(inline_query):
+    """ Output in the console about current user actions and status of bot
+    :type inline_query: 
+    :param inline_query:
+
+    :raises: none
+
+    :rtype: none
+    """
+    try:
+        r = types.InlineQueryResultArticle('1', 'Result1', types.InputTextMessageContent('hi'))
+        r2 = types.InlineQueryResultArticle('2', 'Result2', types.InputTextMessageContent('hi'))
+        bot.answer_inline_query(inline_query.id, [r, r2])
+    except Exception as e:
+        print(e)
+
+
+def main_loop():
+    """ Start bot
+    :raises: none
+
+    :rtype: none
+    """
+    bot.infinity_polling()
+
+
+if __name__ == '__main__':
+    try:
+        main_loop()
+    except KeyboardInterrupt:
+        print('\nExiting by user request.\n')
+        sys.exit(0)
+
+
+
+
+
+
+
+

Functions

+
+
+def add_keyword(message) +
+
+

Add keyword to user +:type message: message object bot +:param message: message that was reacted to, in this case always '/addkeyword'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['addkeyword', 'Addkeyword'])  # /addkeyword -> add keyword to user
+def add_keyword(message):
+    """ Add keyword to user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/addkeyword'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type keyword to add:')
+    bot.register_next_step_handler(message, store_keyword)  # wait for user to send keyword, then call store_keyword function
+
+
+
+def echo_all(message) +
+
+

Tell that command is not known if it is no known command +:type message: message object bot +:param message: message that was reacted to, if no other command handler gets called

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(func=lambda message: True)  # Returning that command is unknown for any other statement
+def echo_all(message):
+    """ Tell that command is not known if it is no known command
+    :type message: message object bot
+    :param message: message that was reacted to, if no other command handler gets called
+
+    :raises: none
+
+    :rtype: none
+    """
+    answer = 'Do not know this command or text: ' + message.text
+    bot.reply_to(message, answer)
+
+
+
+def main_loop() +
+
+

Start bot +:raises: none

+

:rtype: none

+
+ +Expand source code + +
def main_loop():
+    """ Start bot
+    :raises: none
+
+    :rtype: none
+    """
+    bot.infinity_polling()
+
+
+
+def query_text(inline_query) +
+
+

Output in the console about current user actions and status of bot +:type inline_query: +:param inline_query:

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.inline_handler(lambda query: query.query == 'text')  # inline prints for debugging
+def query_text(inline_query):
+    """ Output in the console about current user actions and status of bot
+    :type inline_query: 
+    :param inline_query:
+
+    :raises: none
+
+    :rtype: none
+    """
+    try:
+        r = types.InlineQueryResultArticle('1', 'Result1', types.InputTextMessageContent('hi'))
+        r2 = types.InlineQueryResultArticle('2', 'Result2', types.InputTextMessageContent('hi'))
+        bot.answer_inline_query(inline_query.id, [r, r2])
+    except Exception as e:
+        print(e)
+
+
+
+def remove_keyword(message) +
+
+

Remove keyword from user +:type message: message object bot +:param message: message that was reacted to, in this case always '/removekeyword'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['removekeyword', 'Removekeyword'])  # /removekeyword -> remove keyword from user
+def remove_keyword(message):
+    """ Remove keyword from user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/removekeyword'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type keyword to remove:')
+    bot.register_next_step_handler(message, remove_keyword_step)  # wait for user to send keyword to remove, then call remove_keyword_step function
+
+
+
+def remove_keyword_step(message) +
+
+
+
+ +Expand source code + +
def remove_keyword_step(message):
+    user_id = int(message.from_user.id)
+    keyword = str(message.text).lower()
+    status = api_handler.delete_keyword(user_id, keyword)
+    if status == 200:  # statuscode 200 means keyword was removed successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.')  # checking if keyword to remove is in database are handled in database, not here
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed deleting keyword "{keyword}". (statuscode {status})')
+
+
+
+def remove_share(message) +
+
+

Remove share from portfolio +:type message: message object bot +:param message: message that was reacted to, in this case always '/removeshare'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['removeshare', 'Removeshare'])
+def remove_share(message):
+    """ Remove share from portfolio
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/removeshare'
+    
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+
+    bot.send_message(chat_id=user_id, text='Type ISIN/Symbol/CompanyName of share to remove (if you are unsure do /shares, find your share and insert the value above amount):')
+    bot.register_next_step_handler(message, remove_share_step)  # wait for user to send ISIN, then call remove_share_step function
+
+
+
+def remove_share_step(message) +
+
+
+
+ +Expand source code + +
def remove_share_step(message):
+    user_id = int(message.from_user.id)
+    isin = str(message.text)
+
+    status = api_handler.delete_share(int(user_id), str(isin))  # remove share from portfolio
+
+    if status == 200:  # statuscode 200 means share was removed successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Share "{isin}" removed.')  # checking if share to remove is in database are handled in database, not here
+
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed deleting share "{isin}". (statuscode {status})\nMake sure that the share is in your portfolio and written exactly like there.')
+
+
+
+def send_all_news(message) +
+
+

Get news for keywords of user +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/allnews'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['allnews', 'Allnews'])  # /allnews -> get all news
+def send_all_news(message):
+    """ Get news for keywords of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/allnews'
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword')
+        return
+
+    keywords_search = ' OR '.join(keywords)  # concat all keywords with OR -> NewsAPI can understand OR, AND, NOT etc.
+    now = dt.datetime.now().date()  # get current date
+    from_date = now - dt.timedelta(days=7)  # get date 7 days ago -> limit age of news to 7 days old max
+    from_date_formatted = dt.datetime.strftime(from_date, '%Y-%m-%d')
+    news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"]  # array of JSON article objects
+
+    if news_list:  # true if news_list is not empty
+        for article in news_list:
+            formatted_article = news.format_article(article)
+            bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWNV2")  # Markdown allows to write bold text with * etc.
+    else:
+        bot.send_message(chat_id=user_id, text='No news found for your keywords.')
+
+
+
+def send_all_users(message) +
+
+

Send all users, only possible for admins +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/users'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['users', 'Users'])  # /users -> sending all users
+def send_all_users(message):
+    """ Send all users, only possible for admins
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/users'
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+    if (user_data["admin"] == False):  # check if user has admin rights
+        bot.reply_to(message, "You have to be an admin to use this command")
+        return
+
+    user_list = api_handler.get_all_users()
+    user_count = len(user_list)
+    bot.send_message(chat_id=user_id, text="There are " + str(user_count) + " users in the database:")
+
+    for user in user_list:
+        username = user['username']
+        email = user['email']
+        id = user['telegram_user_id']
+        cron = user['cron']
+        admin = user['admin']
+
+        bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {id}\nCron: {cron}\nAdmin: {admin}')  # format user data into readable message text
+
+
+
+def send_help(message) +
+
+

Send all functions +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/help'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['help', 'Help'])  # /help -> sending all functions
+def send_help(message):
+    """ Send all functions
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/help'
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message,
+                 "/id or /auth get your user id\n/update get updates on your shares.\n/shares get update on interesting shares\n/setAdmin set admin rights of user (ADMIN)\n/users see all users. (ADMIN)\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/transactions get all transactions\n/newtransaction create new transaction\n/share get price of specific share\n/portfolio see own portfolio\n/removeshare removes share from portfolio\n/interval get update interval\n/setinterval set update interval\n For further details see https://gruppe1.testsites.info")
+
+
+
+def send_id(message) +
+
+

Send user id for authentication with browser +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/id' or '/auth'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['id', 'auth', 'Id', 'Auth'])  # /auth or /id -> Authentication with user_id over web tool
+def send_id(message):
+    """ Send user id for authentication with browser
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/id' or '/auth'
+
+    :raises: none
+
+    :rtype: none
+    """
+    answer = 'Your ID/Authentication Code is: [' + str(message.from_user.id) + ']. Enter this code in the settings on https://gruppe1.testsites.info to get updates on your shares.'
+    bot.reply_to(message, answer)
+
+
+
+def send_interval(message) +
+
+

send interval for user +:type message: message object bot +:param message: message that was reacted to, in this case always '/interval'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['interval', 'Interval'])
+def send_interval(message):
+    """ send interval for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/interval'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)  # get cron interval of user (stored in user data)
+    if user_data == None:  # true if user is not registered in DB
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval')
+        return
+    else:  # send interval
+        interval = str(user_data['cron'])  # get cron from user data
+        if interval == 'None':  # true if user has no cron set
+            bot.send_message(chat_id=user_id, text='You do not have an interval set. Set one with /setinterval')
+            return
+        formatted_interval = str(interval).replace(' ', '_')  # replace spaces with underscores to add to url of crontab.guru
+        bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})')
+
+
+
+def send_keywords(message) +
+
+

Send keywords of user +:type message: message object bot +:param message: message that was reacted to, in this case always '/keywords'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['keywords', 'Keywords'])  # /keywords -> get keywords of user
+def send_keywords(message):
+    """ Send keywords of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/keywords'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='No keywords set for this account. Add keywords by using /addkeyword')
+        return
+
+    else:  # send keyword list
+        keywords_str = ', '.join(keywords)
+        keywords_str = hf.make_markdown_proof(keywords_str)
+
+        text = f'Your keywords are: _{keywords_str}_'
+        bot.send_message(chat_id=user_id, text=text, parse_mode="MARKDOWNV2")
+
+
+
+def send_news(message) +
+
+

Get news for keywords of user

+

:type message: message object bot +:param message: message that was reacted to, in this case always containing '/news'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['news', 'News'])  # /news -> get news for specific keyword
+def send_news(message):
+    """ Get news for keywords of user
+
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/news'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    keywords = api_handler.get_user_keywords(user_id)  # get keywords of user
+
+    if keywords == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not keywords:  # true if user is registered but does not have any keywords
+        bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword')
+        return
+
+    if keywords:
+        for keyword in keywords:
+            top_news = news.get_top_news_by_keyword(keyword)["articles"]
+            if top_news == None:  # true if request to NewsAPI failed
+                bot.send_message(chat_id=user_id, text='News Server did not respond correctly. Try again later.')
+
+            if not top_news:  # true if no news found for keyword (empty list)
+                keyword = hf.make_markdown_proof(keyword)
+                bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWNV2")
+
+            else:
+                keyword = hf.make_markdown_proof(keyword)
+                formatted_article = news.format_article(top_news[0])  # only format and send most popular news
+                bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWNV2")  # do not use v2 because of bugs related t "." in links
+
+
+
+def send_portfolio(message) +
+
+

Send portfolio of user +:type message: message object bot +:param message: message that was reacted to, in this case always '/portfolio'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['portfolio', 'Portfolio'])
+def send_portfolio(message):
+    """ Send portfolio of user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/portfolio'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    portfolio = api_handler.get_user_portfolio(user_id)  # get portfolio of user as json
+    if portfolio == None:  # true if user is not registered
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+    if not portfolio:  # true if user is registered but does not have any stocks in portfolio
+        bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.')
+        return
+    else:  # send portfolio
+        for stock in portfolio:
+            comment = hf.make_markdown_proof(str(stock["comment"]))  # comment may be written name of stock, comment is made by user when adding an stock to portfolio
+            count = hf.make_markdown_proof("{:.2f}".format(float(stock["count"])))  # round count to 2 decimal places
+            isin = hf.make_markdown_proof(str(stock["isin"]))
+            worth = hf.make_markdown_proof("{:.2f}".format(float(stock["current_price"]) * float(stock["count"])))  # round current_price to 2 decimal places
+            bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWNV2")  # formatted message in markdown
+
+
+
+def send_share_price(message) +
+
+
+
+ +Expand source code + +
def send_share_price(message):
+    str_share_price = share_fetcher.get_share_information_markdown(str(message.text))
+    bot.reply_to(message, str_share_price, parse_mode="MARKDOWNV2")
+
+
+
+def send_share_update(message) +
+
+

Send price of a specific share +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/share'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['share', 'Share'])  # /share -> get share price
+def send_share_update(message):
+    """ Send price of a specific share
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/share'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+
+    bot.send_message(chat_id=user_id, text='Send Symbol/ISIN of share or name of company:')
+    bot.register_next_step_handler(message, send_share_price)
+
+
+
+def send_shares(message) +
+
+

send shares for user +:type message: message object bot +:param message: message that was reacted to, in this case always '/shares'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['shares', 'Shares'])
+def send_shares(message):
+    """ send shares for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/shares'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    shares = api_handler.get_user_shares(user_id)  # get shares of user
+
+    if shares == None:  # true if user does not exist
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+    elif not shares:  # true if user has no shares
+        bot.send_message(chat_id=user_id, text='You do not have any shares. Add shares on https://gruppe1.testsites.info')
+    else:
+        for element in shares:
+            bot.send_message(chat_id=user_id, text=share_fetcher.get_share_information_markdown(element), parse_mode="MARKDOWNV2")
+
+
+
+def send_start(message) +
+
+

Sending welcome message to new user +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/start'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['start', 'Start'])
+def send_start(message):
+    """ Sending welcome message to new user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/start'
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message, "Welcome to this share bot project. \
+                 \nType /help to get information on what this bot can do. \
+                 \nAlso see https://gruppe1.testsites.info \
+                 to start configuring your bot")
+
+
+
+def send_status(message) +
+
+

Sends status to user +:type message: message object bot +:param message: message that was reacted to, if no other command handler gets called

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['status', 'Status'])
+def send_status(message):
+    """ Sends status to user
+    :type message: message object bot
+    :param message: message that was reacted to, if no other command handler gets called
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.reply_to(message, "bot is running")
+
+
+
+def send_to_user(pText, pUser_id) +
+
+

Send message to user +:type pText: string +:param pText: Text to send to user

+

:type pUser_id: int +:param pUser_id: user to send to. per default me (Florian Kellermann)

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
def send_to_user(pText, pUser_id):
+    """ Send message to user
+    :type pText: string
+    :param pText: Text to send to user
+
+    :type pUser_id: int
+    :param pUser_id: user to send to. per default me (Florian Kellermann)
+
+    :raises: none
+
+    :rtype: none
+    """
+    bot.send_message(chat_id=pUser_id, text=pText)
+
+
+
+def send_transactions(message) +
+
+

send transactions for user +:type message: message object bot +:param message: message that was reacted to, in this case always '/transactions'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['transactions', 'Transactions'])
+def send_transactions(message):
+    """ send transactions for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/transactions'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    transactions = api_handler.get_user_transactions(user_id)  # get transactions of user
+
+    if transactions == None:  # true if user does not exist
+        bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info')
+        return
+
+    if not transactions:  # true if user has no transactions
+        bot.send_message(chat_id=user_id, text='You do not have any transactions.')
+        return
+
+    else:
+
+        for transaction in transactions:
+            comment = hf.make_markdown_proof(transaction['comment']) or "\(no desc\)"  # if comment is empty, make it "no desc"
+            isin = hf.make_markdown_proof(transaction['isin'])
+            amount = hf.make_markdown_proof(transaction['count'])
+            price = hf.make_markdown_proof(transaction['price'])
+            time = hf.make_markdown_proof(transaction['time'])
+
+            bot.send_message(chat_id=user_id, text=f'_{comment}_\n{isin}\namount: {amount}\nprice: {price}\ntime: {time}', parse_mode="MARKDOWNV2")
+
+
+
+def send_user(message) +
+
+

Send user data +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/me'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['me', 'Me'])  # /me -> sending user info
+def send_user(message):
+    """ Send user data
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/me'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+    if not user_data or user_data == None:  # true if user is not registered
+        bot.reply_to(message, "This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info")
+        return
+    username = user_data['username']
+    email = user_data['email']
+    user_id = user_data['telegram_user_id']
+    cron = user_data['cron']
+    admin = user_data['admin']
+    bot.reply_to(message, f'Username: {username}\nEmail: {email}\nID: {user_id}\nCron: {cron}\nAdmin: {admin}')  # format user data into readable message text
+
+
+
+def send_version(message) +
+
+

Sending programm version +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/version'

+

:raises: none

+

:rtype:none

+
+ +Expand source code + +
@bot.message_handler(commands=['version', 'Version'])
+def send_version(message):
+    """ Sending programm version
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/version'
+
+    :raises: none
+
+    :rtype:none
+    """
+    bot.reply_to(message, "the current bot version is " + bot_version)
+
+
+
+def set_admin(message) +
+
+

Set admin rights to user +:type message: message object bot +:param message: message that was reacted to, in this case always containing '/setAdmin'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['setAdmin', 'SetAdmin', 'setadmin', 'Setadmin'])  # set admin rights to user TBD: not working!!
+def set_admin(message):
+    """ Set admin rights to user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always containing '/setAdmin'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    user_data = api_handler.get_user(user_id)
+
+    if (user_data["admin"] == False):  # check if user has admin rights
+        bot.reply_to(message, "You have to be an admin to use this command")
+        return
+
+    bot.send_message(chat_id=user_id, text='send email and true if this account should have admin rights, else false\n in format: <email>,<is_admin>')  # request email and admin rights to change to
+    bot.register_next_step_handler(message, set_admin_step)
+
+
+
+def set_admin_step(message) +
+
+
+
+ +Expand source code + +
def set_admin_step(message):
+    str_message = str(message.text)
+    args_message = str_message.split(',')  # split message into email and admin rights
+
+    if len(args_message) != 2:  # make sure 2 args (email,is_admin) are given
+
+        bot.reply_to(message, "exactly 2 arguments (<email>,<is_admin>) required, try again")
+        return
+
+    email = args_message[0]
+    is_admin = False  # default: False
+
+    if args_message[1].lower() == "true":  # if user types true, set is_admin to true
+        is_admin = True
+
+    status = api_handler.set_admin(email, is_admin)  # set admin in db
+
+    if (status == 200):
+        bot.reply_to(message, "Admin rights set")
+
+    else:
+        bot.reply_to(message, f"Admin rights could not be set ({status})")
+
+
+
+def set_new_interval(message) +
+
+

Set new interval for user +:type message: message object bot +:param message: message that was reacted to, in this case always '/setinterval'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['setinterval', 'Setinterval'])
+def set_new_interval(message):
+    """ Set new interval for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/setinterval'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id, text='Type interval in cron format:\n(https://crontab.guru/)')
+    bot.register_next_step_handler(message, set_new_interval_step)  # executes function when user sends message
+
+
+
+def set_new_interval_step(message) +
+
+
+
+ +Expand source code + +
def set_new_interval_step(message):
+    user_id = int(message.from_user.id)
+    interval = str(message.text)
+    status = api_handler.set_cron_interval(user_id, interval)  # send cron to db
+
+    if status == 200:
+        bot.send_message(chat_id=user_id, text='Interval succesfully set.')
+        return
+
+    if status == -1:  # only -1 when interval is invalid, not a real statuscode, but used from api_handler.set_cron_interval to tell the crontab has the wrong format
+        bot.send_message(chat_id=user_id, text='Invalid interval format. Try again with\n /setinterval.')
+        return
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed setting interval. (statuscode {status})')
+
+
+
+def set_new_transaction(message) +
+
+

Set new transaction for user +:type message: message object bot +:param message: message that was reacted to, in this case always '/newtransaction'

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
@bot.message_handler(commands=['newtransaction', 'Newtransaction'])  # tbd not working rn may be deleted in future
+def set_new_transaction(message):
+    """ Set new transaction for user
+    :type message: message object bot
+    :param message: message that was reacted to, in this case always '/newtransaction'
+
+    :raises: none
+
+    :rtype: none
+    """
+    user_id = int(message.from_user.id)
+    bot.send_message(chat_id=user_id,
+                     text='Type "<name of stock>,<isin/name/symbol>,<amount>,<price_per_stock_usd>" (time of transaction will be set to now, negative amount is selling, positive is buying):')
+    bot.register_next_step_handler(message, set_new_transaction_step)
+
+
+
+def set_new_transaction_step(message) +
+
+
+
+ +Expand source code + +
def set_new_transaction_step(message):
+    user_id = int(message.from_user.id)
+
+    if not re.match(r"[A-Za-z0-9 ]+,[A-Za-z0-9]+,(-)?[0-9]+(.[0-9]+)?,[0-9]+(.[0-9]+)?", message.text):
+        bot.send_message(chat_id=user_id, text='Invalid format \n(e.g. Apple,US0378331005,53.2,120.4).\n Try again with /newtransaction.')
+        return
+
+    transaction_data = str(message.text).split(',')
+    desc = str(transaction_data[0])
+    isin = str(transaction_data[1])
+    amount = float(transaction_data[2])
+    price = float(transaction_data[3])
+    time = dt.datetime.now().isoformat()
+    print("\n\n\n\n\n")
+    print(f"{isin},{amount},{price},{time}")
+    status = api_handler.set_transaction(user_id, desc, isin, amount, price, time)
+
+    if status == 200:
+        bot.send_message(chat_id=user_id, text='Transaction succesfully added.')
+    else:
+        bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})')
+
+
+
+def store_keyword(message) +
+
+
+
+ +Expand source code + +
def store_keyword(message):
+    user_id = int(message.from_user.id)
+    keyword = str(message.text).lower()  # lower to ensure Bitcoin and bitcoin is not stored as individual keywords
+    status = api_handler.set_keyword(user_id, keyword)  # set keyword in database
+    if status == 200:  # statuscode 200 means keyword was added successfully without errors
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.')  # duplicate keywords are denied by Database, so no need to check for that here
+    else:
+        bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info (statuscode {status})')
+
+
+
+def update_for_user(message) +
+
+
+
+ +Expand source code + +
@bot.message_handler(commands=['update', 'Update'])  # /update -> update shares
+def update_for_user(message):
+    p_user_id = int(message.from_user.id)
+    p_my_handler = api_handler
+
+    share_symbols = []
+    share_amounts = []
+
+    my_portfolio = p_my_handler.get_user_portfolio(p_user_id)
+
+    for element in my_portfolio:
+        if element["count"] != '' and element["isin"] != '':
+            print(element["count"], element["isin"])
+            share_symbols.append(element["isin"])
+            share_amounts.append(element["count"])
+
+    my_user = p_my_handler.get_user(p_user_id)
+    send_to_user("Hello %s this is your share update:" % str(my_user["username"]), pUser_id=p_user_id)
+
+    if len(share_symbols) != 0:
+        for i in range(len(share_symbols)):
+            my_price = share_fetcher.get_share_price_no_currency(share_symbols[i])
+            my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {share_amounts[i]}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR'
+            bot.send_message(chat_id=p_user_id, text=my_update_message, parse_mode="MARKDOWNV2")
+    else:
+        send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/bot_updates.html b/documentation/telegram_bot/bot_updates.html new file mode 100644 index 0000000..6a76576 --- /dev/null +++ b/documentation/telegram_bot/bot_updates.html @@ -0,0 +1,507 @@ + + + + + + +telegram_bot.bot_updates API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.bot_updates

+
+
+

script for regularly sending updates on shares and news based on user interval

+
+ +Expand source code + +
"""
+script for regularly sending updates on shares and news based on user interval
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "10.05.2022"
+__version__ = "1.0.2"
+__license__ = "None"
+
+import os
+import sys
+import time
+
+from apscheduler.schedulers.background import BackgroundScheduler # scheduler for cron
+from dotenv import load_dotenv
+
+import telegram_bot.helper_functions as hf
+import telegram_bot.news.news_fetcher as news_fetcher
+import telegram_bot.shares.share_fetcher as share_fetcher
+from telegram_bot.api_handling.api_handler import API_Handler
+from telegram_bot.bot import bot
+
+'''
+* * * * * code
+┬ ┬ ┬ ┬ ┬
+│ │ │ │ │
+│ │ │ │ └──── weekday (0->Monday, 7->Sunday)
+│ │ │ └────── Month (1-12)
+│ │ └──────── Day (1-31)
+│ └────────── Hour (0-23)
+└──────────── Minute (0-59)
+
+example 0 8 * * * -> daily update at 8am
+'''
+user_ids = []
+user_crontab = []
+
+load_dotenv(dotenv_path='.env')
+
+
+def start_updater():
+    """ starting function for regularly sending updates
+    :raises: none
+
+    :rtype: none
+    """
+
+    print("Bot updates started")
+
+    my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD")))
+
+    update_crontab(my_handler)
+
+
+def update_crontab(p_my_handler):
+    """ Updating crontab lists every hour
+    :type pCurrent_Time: time when starting crontab update
+    :param pCurrent_Time: datetime
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    global user_crontab
+    global user_ids
+    
+    all_users = p_my_handler.get_all_users() # get all users so crontabs can update for everybody
+    
+    user_ids = []
+    user_crontab = []
+
+    for element in all_users:
+        if element["cron"] != '' and element["telegram_user_id"] != '': # check if both values are existing so I have consistent data
+            try:
+                user_ids.append(int(element["telegram_user_id"]))
+                try:
+                    user_crontab.append(str(element["cron"]))
+                except: 
+                    user_ids.pop() # if something goes wrong with cron I have to delete matching user id 
+            except: continue
+                
+    
+    print(user_ids)
+
+    update_based_on_crontab(user_ids, user_crontab, p_my_handler)
+    
+    update_crontab(p_my_handler) # restart the update after time sleep
+    
+    
+def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler):
+    """ Check all the crontab codes and add jobs to start in time
+    :type p_user_ids: array
+    :param p_user_ids: user id array of all users
+
+    :type p_user_crontab: array
+    :param p_user_crontab: crontabs for all users equivalent to the user array
+
+    :type p_my_handler: Api_Handler
+    :param p_my_handler: get database stuff
+
+    :raises: none
+
+    :rtype: none
+    """
+    
+    my_scheduler = BackgroundScheduler() # schedule sends based on cron
+    
+    for i in range(len(p_user_ids)):
+        cron_split = p_user_crontab[i].split(" ") # split it up to use in scheduler
+        print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2])
+        my_scheduler.add_job(update_for_user, 'cron', day_of_week=cron_split[4], hour=cron_split[1], minute=cron_split[0], month=cron_split[3], day=cron_split[2], args=(p_user_ids[i], p_my_handler))
+
+    my_scheduler.start()
+    
+    time.sleep( 600 ) # scheduler runs in background and I wait 10mins
+    my_scheduler.shutdown() # after this the new crontabs will be loaded
+                    
+def update_for_user(p_user_id, p_my_handler):
+    """ Pull shares and send updates for specific user id
+    :type p_user_id: integer
+    :param p_user_id: user id of user that shall receive update
+
+    :type p_my_handler: Api_Handler
+    :param p_my_handler: handle the api and pull from database
+
+    :raises: none
+
+    :rtype: none
+    """
+    share_symbols = []
+    share_amounts = []
+    
+    my_portfolio = p_my_handler.get_user_portfolio(p_user_id) # get all existing shares for user
+    
+    for element in my_portfolio:
+        if element["count"] != '' and element["isin"] != '':
+            print(element["count"], element["isin"])
+            share_symbols.append(element["isin"])
+            share_amounts.append(element["count"])
+
+    my_user = p_my_handler.get_user(p_user_id)
+    send_to_user("Hello %s this is your share update for today:"%str(my_user["username"]), pUser_id=p_user_id)
+    
+    shares = p_my_handler.get_user_shares(p_user_id) # all interest shares
+    
+    if len(share_symbols) != 0: # iterate through all shares
+        for i in range(len(share_symbols)):
+            my_price = share_fetcher.get_share_price_no_currency(share_symbols[i])
+            my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {hf.make_markdown_proof(share_amounts[i])}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR'
+            bot.send_message(chat_id=p_user_id, text=my_update_message, parse_mode="MARKDOWNV2")
+    else:
+        send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id)
+
+    if len(shares) != 0:  # Send updates on watchlist shares if existing
+        send_to_user("Your watchlist shares:", pUser_id=p_user_id)
+        for element in shares:
+            send_to_user(share_fetcher.get_share_information_markdown(element), pUser_id=p_user_id, md_mode=True)
+
+    keywords = p_my_handler.get_user_keywords(p_user_id)  # get keywords as array
+
+    if (keywords):  # if keywords exist and array is not empty
+        send_to_user("If you haven't read yet: \nHere are some interesting news according to your keywords:", pUser_id=p_user_id)
+        for keyword in keywords:
+            news = news_fetcher.get_top_news_by_keyword(keyword)["articles"]
+            keyword = hf.make_markdown_proof(keyword)
+
+            if not news: # if empty news array
+                send_to_user(f"No news found for keyword _{keyword}_\.", pUser_id=p_user_id, md_mode=True)
+            
+            elif news == None: # if news is none
+                send_to_user(f"Server error for keyword _{keyword}_\.", pUser_id=p_user_id, md_mode=True)
+            else:
+                news_formatted = news_fetcher.format_article(news[0])  # format for message, only use the most popular article
+                send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True)  # send news with related keyword in Markdown
+
+
+def send_to_user(pText, pUser_id, md_mode=False):
+    """ Send message to user
+    :type pText: string
+    :param pText: Text to send to user
+
+    :type pUser_id: int
+    :param pUser_id: user to send to. per default me (Florian Kellermann)
+
+    :type md_mode: boolean
+    :param md_mode: if true, parse_mode is markdown
+
+    :raises: none
+
+    :rtype: none
+    """
+    if md_mode:
+        bot.send_message(chat_id=pUser_id, text=pText, parse_mode="MARKDOWNV2")
+    else:
+        bot.send_message(chat_id=pUser_id, text=pText)
+
+
+if __name__ == "__main__":
+    try:
+        start_updater()
+        sys.exit(-1)
+    except KeyboardInterrupt:
+        print("Ending")
+        sys.exit(-1)
+
+
+
+
+
+
+
+

Functions

+
+
+def send_to_user(pText, pUser_id, md_mode=False) +
+
+

Send message to user +:type pText: string +:param pText: Text to send to user

+

:type pUser_id: int +:param pUser_id: user to send to. per default me (Florian Kellermann)

+

:type md_mode: boolean +:param md_mode: if true, parse_mode is markdown

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
def send_to_user(pText, pUser_id, md_mode=False):
+    """ Send message to user
+    :type pText: string
+    :param pText: Text to send to user
+
+    :type pUser_id: int
+    :param pUser_id: user to send to. per default me (Florian Kellermann)
+
+    :type md_mode: boolean
+    :param md_mode: if true, parse_mode is markdown
+
+    :raises: none
+
+    :rtype: none
+    """
+    if md_mode:
+        bot.send_message(chat_id=pUser_id, text=pText, parse_mode="MARKDOWNV2")
+    else:
+        bot.send_message(chat_id=pUser_id, text=pText)
+
+
+
+def start_updater() +
+
+

starting function for regularly sending updates +:raises: none

+

:rtype: none

+
+ +Expand source code + +
def start_updater():
+    """ starting function for regularly sending updates
+    :raises: none
+
+    :rtype: none
+    """
+
+    print("Bot updates started")
+
+    my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD")))
+
+    update_crontab(my_handler)
+
+
+
+def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler) +
+
+

Check all the crontab codes and add jobs to start in time +:type p_user_ids: array +:param p_user_ids: user id array of all users

+

:type p_user_crontab: array +:param p_user_crontab: crontabs for all users equivalent to the user array

+

:type p_my_handler: Api_Handler +:param p_my_handler: get database stuff

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler):
+    """ Check all the crontab codes and add jobs to start in time
+    :type p_user_ids: array
+    :param p_user_ids: user id array of all users
+
+    :type p_user_crontab: array
+    :param p_user_crontab: crontabs for all users equivalent to the user array
+
+    :type p_my_handler: Api_Handler
+    :param p_my_handler: get database stuff
+
+    :raises: none
+
+    :rtype: none
+    """
+    
+    my_scheduler = BackgroundScheduler() # schedule sends based on cron
+    
+    for i in range(len(p_user_ids)):
+        cron_split = p_user_crontab[i].split(" ") # split it up to use in scheduler
+        print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2])
+        my_scheduler.add_job(update_for_user, 'cron', day_of_week=cron_split[4], hour=cron_split[1], minute=cron_split[0], month=cron_split[3], day=cron_split[2], args=(p_user_ids[i], p_my_handler))
+
+    my_scheduler.start()
+    
+    time.sleep( 600 ) # scheduler runs in background and I wait 10mins
+    my_scheduler.shutdown() # after this the new crontabs will be loaded
+
+
+
+def update_crontab(p_my_handler) +
+
+

Updating crontab lists every hour +:type pCurrent_Time: time when starting crontab update +:param pCurrent_Time: datetime

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
def update_crontab(p_my_handler):
+    """ Updating crontab lists every hour
+    :type pCurrent_Time: time when starting crontab update
+    :param pCurrent_Time: datetime
+
+    :raises: none
+
+    :rtype: none
+    """
+
+    global user_crontab
+    global user_ids
+    
+    all_users = p_my_handler.get_all_users() # get all users so crontabs can update for everybody
+    
+    user_ids = []
+    user_crontab = []
+
+    for element in all_users:
+        if element["cron"] != '' and element["telegram_user_id"] != '': # check if both values are existing so I have consistent data
+            try:
+                user_ids.append(int(element["telegram_user_id"]))
+                try:
+                    user_crontab.append(str(element["cron"]))
+                except: 
+                    user_ids.pop() # if something goes wrong with cron I have to delete matching user id 
+            except: continue
+                
+    
+    print(user_ids)
+
+    update_based_on_crontab(user_ids, user_crontab, p_my_handler)
+    
+    update_crontab(p_my_handler) # restart the update after time sleep
+
+
+
+def update_for_user(p_user_id, p_my_handler) +
+
+

Pull shares and send updates for specific user id +:type p_user_id: integer +:param p_user_id: user id of user that shall receive update

+

:type p_my_handler: Api_Handler +:param p_my_handler: handle the api and pull from database

+

:raises: none

+

:rtype: none

+
+ +Expand source code + +
def update_for_user(p_user_id, p_my_handler):
+    """ Pull shares and send updates for specific user id
+    :type p_user_id: integer
+    :param p_user_id: user id of user that shall receive update
+
+    :type p_my_handler: Api_Handler
+    :param p_my_handler: handle the api and pull from database
+
+    :raises: none
+
+    :rtype: none
+    """
+    share_symbols = []
+    share_amounts = []
+    
+    my_portfolio = p_my_handler.get_user_portfolio(p_user_id) # get all existing shares for user
+    
+    for element in my_portfolio:
+        if element["count"] != '' and element["isin"] != '':
+            print(element["count"], element["isin"])
+            share_symbols.append(element["isin"])
+            share_amounts.append(element["count"])
+
+    my_user = p_my_handler.get_user(p_user_id)
+    send_to_user("Hello %s this is your share update for today:"%str(my_user["username"]), pUser_id=p_user_id)
+    
+    shares = p_my_handler.get_user_shares(p_user_id) # all interest shares
+    
+    if len(share_symbols) != 0: # iterate through all shares
+        for i in range(len(share_symbols)):
+            my_price = share_fetcher.get_share_price_no_currency(share_symbols[i])
+            my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {hf.make_markdown_proof(share_amounts[i])}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR'
+            bot.send_message(chat_id=p_user_id, text=my_update_message, parse_mode="MARKDOWNV2")
+    else:
+        send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id)
+
+    if len(shares) != 0:  # Send updates on watchlist shares if existing
+        send_to_user("Your watchlist shares:", pUser_id=p_user_id)
+        for element in shares:
+            send_to_user(share_fetcher.get_share_information_markdown(element), pUser_id=p_user_id, md_mode=True)
+
+    keywords = p_my_handler.get_user_keywords(p_user_id)  # get keywords as array
+
+    if (keywords):  # if keywords exist and array is not empty
+        send_to_user("If you haven't read yet: \nHere are some interesting news according to your keywords:", pUser_id=p_user_id)
+        for keyword in keywords:
+            news = news_fetcher.get_top_news_by_keyword(keyword)["articles"]
+            keyword = hf.make_markdown_proof(keyword)
+
+            if not news: # if empty news array
+                send_to_user(f"No news found for keyword _{keyword}_\.", pUser_id=p_user_id, md_mode=True)
+            
+            elif news == None: # if news is none
+                send_to_user(f"Server error for keyword _{keyword}_\.", pUser_id=p_user_id, md_mode=True)
+            else:
+                news_formatted = news_fetcher.format_article(news[0])  # format for message, only use the most popular article
+                send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True)  # send news with related keyword in Markdown
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/helper_functions.html b/documentation/telegram_bot/helper_functions.html new file mode 100644 index 0000000..5f08a9a --- /dev/null +++ b/documentation/telegram_bot/helper_functions.html @@ -0,0 +1,220 @@ + + + + + + +telegram_bot.helper_functions API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.helper_functions

+
+
+

script for helper functions for bot related stuff

+
+ +Expand source code + +
"""
+script for helper functions for bot related stuff
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "10.05.2022"
+__version__ = "1.0.0"
+__license__ = "None"
+
+
+def contains_markdownv1_symbols(text):
+    """ checks if text contains markdown symbols
+    :type text: string
+
+    :param text: text to check
+
+    :return: true if text contains markdown symbols
+
+    :rtype: bool
+    """
+    if text.find("_") != -1 or text.find("*") != -1 or text.find("`") != -1:  # check if text contains relevant markdown symbols
+        return True
+
+    return False
+
+
+def make_markdown_proof(text):  # used to avoid errors related to markdown parsemode for telegram messaging
+    """ makes text markdown proof
+    :type text: string
+
+    :param text: text to make markdown proof
+
+    :return: markdown proof text
+
+    :rtype: string
+    """
+    text = str(text)
+
+    text = text.replace("_", "\\_")  # replace _ with \_ because \ is used as escape character in markdown, double escape is needed because \ is also a escape character in strings
+    text = text.replace("*", "\\*")
+    text = text.replace("`", "\\`")
+    text = text.replace("[", "\\[")
+    text = text.replace("]", "\\]")
+    text = text.replace("(", "\\(")
+    text = text.replace(")", "\\)")
+    text = text.replace("#", "\\#")
+    text = text.replace("+", "\\+")
+    text = text.replace("-", "\\-")
+    text = text.replace("!", "\\!")
+    text = text.replace(".", "\\.")
+    text = text.replace("?", "\\?")
+    text = text.replace("/", "\\/")
+    text = text.replace("~", "\\~")
+    text = text.replace("|", "\\|")
+    text = text.replace("<", "\\<")
+    text = text.replace(">", "\\>")
+    text = text.replace("&", "\\&")
+    text = text.replace("^", "\\^")
+    text = text.replace("$", "\\$")
+    text = text.replace("%", "\\%")
+    text = text.replace("=", "\\=")
+    text = text.replace("@", "\\@")
+
+    return text
+
+
+if __name__ == '__main__':
+    print("this is a module for helper functions for the bot and should not be run directly")
+    print(make_markdown_proof("_test_"))
+    text = make_markdown_proof("_test_")
+    print(f"{text}")
+
+
+
+
+
+
+
+

Functions

+
+
+def contains_markdownv1_symbols(text) +
+
+

checks if text contains markdown symbols +:type text: string

+

:param text: text to check

+

:return: true if text contains markdown symbols

+

:rtype: bool

+
+ +Expand source code + +
def contains_markdownv1_symbols(text):
+    """ checks if text contains markdown symbols
+    :type text: string
+
+    :param text: text to check
+
+    :return: true if text contains markdown symbols
+
+    :rtype: bool
+    """
+    if text.find("_") != -1 or text.find("*") != -1 or text.find("`") != -1:  # check if text contains relevant markdown symbols
+        return True
+
+    return False
+
+
+
+def make_markdown_proof(text) +
+
+

makes text markdown proof +:type text: string

+

:param text: text to make markdown proof

+

:return: markdown proof text

+

:rtype: string

+
+ +Expand source code + +
def make_markdown_proof(text):  # used to avoid errors related to markdown parsemode for telegram messaging
+    """ makes text markdown proof
+    :type text: string
+
+    :param text: text to make markdown proof
+
+    :return: markdown proof text
+
+    :rtype: string
+    """
+    text = str(text)
+
+    text = text.replace("_", "\\_")  # replace _ with \_ because \ is used as escape character in markdown, double escape is needed because \ is also a escape character in strings
+    text = text.replace("*", "\\*")
+    text = text.replace("`", "\\`")
+    text = text.replace("[", "\\[")
+    text = text.replace("]", "\\]")
+    text = text.replace("(", "\\(")
+    text = text.replace(")", "\\)")
+    text = text.replace("#", "\\#")
+    text = text.replace("+", "\\+")
+    text = text.replace("-", "\\-")
+    text = text.replace("!", "\\!")
+    text = text.replace(".", "\\.")
+    text = text.replace("?", "\\?")
+    text = text.replace("/", "\\/")
+    text = text.replace("~", "\\~")
+    text = text.replace("|", "\\|")
+    text = text.replace("<", "\\<")
+    text = text.replace(">", "\\>")
+    text = text.replace("&", "\\&")
+    text = text.replace("^", "\\^")
+    text = text.replace("$", "\\$")
+    text = text.replace("%", "\\%")
+    text = text.replace("=", "\\=")
+    text = text.replace("@", "\\@")
+
+    return text
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/index.html b/documentation/telegram_bot/index.html new file mode 100644 index 0000000..ed6c2bb --- /dev/null +++ b/documentation/telegram_bot/index.html @@ -0,0 +1,85 @@ + + + + + + +telegram_bot API documentation + + + + + + + + + + + +
+
+
+

Namespace telegram_bot

+
+
+
+
+

Sub-modules

+
+
telegram_bot.api_handling
+
+
+
+
telegram_bot.bot
+
+

script for telegram bot and its functions

+
+
telegram_bot.bot_updates
+
+

script for regularly sending updates on shares and news based on user interval

+
+
telegram_bot.helper_functions
+
+

script for helper functions for bot related stuff

+
+
telegram_bot.news
+
+
+
+
telegram_bot.shares
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/news/index.html b/documentation/telegram_bot/news/index.html new file mode 100644 index 0000000..76fbf5f --- /dev/null +++ b/documentation/telegram_bot/news/index.html @@ -0,0 +1,65 @@ + + + + + + +telegram_bot.news API documentation + + + + + + + + + + + +
+
+
+

Namespace telegram_bot.news

+
+
+
+
+

Sub-modules

+
+
telegram_bot.news.news_fetcher
+
+

script for news fetching (by keywords)

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/news/news_fetcher.html b/documentation/telegram_bot/news/news_fetcher.html new file mode 100644 index 0000000..9211d75 --- /dev/null +++ b/documentation/telegram_bot/news/news_fetcher.html @@ -0,0 +1,264 @@ + + + + + + +telegram_bot.news.news_fetcher API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.news.news_fetcher

+
+
+

script for news fetching (by keywords)

+
+ +Expand source code + +
"""
+script for news fetching (by keywords)
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "26.04.2022"
+__version__ = "1.0.0"
+__license__ = "None"
+
+import os
+import sys
+
+import telegram_bot.helper_functions as hf
+import requests
+from dotenv import load_dotenv
+from newsapi import NewsApiClient
+
+load_dotenv()  # loads environment vars
+
+# Init
+api_key = os.getenv('NEWS_API_KEY')  # get API Key from .env file
+newsapi = NewsApiClient(api_key=api_key)  # news api from https://newsapi.org/
+
+try:
+    # get all available news sources (e.g BBC, New York Times, etc.)
+    source_json = requests.get(f"https://newsapi.org/v2/top-headlines/sources?apiKey={api_key}&language=en").json()
+    sources = source_json["sources"]
+    str_sources = ",".join([source["id"] for source in sources])
+
+except KeyError:
+    print("Error: Could not get sources, may be blocked because of too many requests (free newsapi is limited to 100 reqs per day)")
+    str_sources = str(
+        "Reuters, bbc, cnn, fox-news, google-news, hacker-news, nytimes, the-huffington-post, the-new-york-times, business-insider, bbc-news, cbc-news, ESPN, fox-sports, google-news-uk, independent, the-wall-street-journal, the-washington-times, time, usa-today")
+
+
+def get_all_news_by_keyword(keyword, from_date="2000-01-01"):
+    """get all news to keyword
+    Args:
+        keyword (String): keyword for search
+        from_date (String): min date for search
+
+    Returns:
+        JSON/dict: dict containing articles
+    """
+    top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date)  # keywords can be combined with OR (e.g. keyword = "bitcoin OR ethereum")
+    if (top_headlines["status"] == "ok"):
+        return top_headlines
+    else:
+        return None
+
+
+def get_top_news_by_keyword(keyword):
+    """get top news to keyword
+    Args:
+        keyword (String): keyword for search
+
+    Returns:
+        JSON/dict: dict containing articles
+    """
+    top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en')  # get top headlines, measured by popularity from NewsApi
+    if (top_headlines["status"] == "ok"):
+        return top_headlines
+    else:
+        return None
+
+
+def format_article(article):
+    """format article for messaging (using markdown syntax)
+
+    Args:
+        article (dict): article to format for messaging
+
+    Returns:
+        String: formatted article
+    """
+    sourcename = hf.make_markdown_proof(article["source"]["name"])  # make attributes markdownv2 proof
+    headline = hf.make_markdown_proof(article["title"])
+    url = hf.make_markdown_proof(article["url"])
+    formatted_article = f"_{sourcename}_\n*{headline}*\n\n{url}"  # formatting in Markdown syntax
+
+    return formatted_article
+
+
+if __name__ == '__main__':  # only execute if script is called directly -> for simple testing
+
+    print("this is a module and should not be run directly")
+    print("fetching top news by keyword bitcoin...")
+
+    articles = get_all_news_by_keyword("bitcoin")
+    formatted_article = format_article(articles["articles"][0])
+    print(formatted_article)
+    articles = get_top_news_by_keyword("bitcoin")
+    formatted_article = format_article(articles["articles"][0])
+    print(formatted_article)
+    sys.exit(1)
+
+
+
+
+
+
+
+

Functions

+
+
+def format_article(article) +
+
+

format article for messaging (using markdown syntax)

+

Args

+
+
article : dict
+
article to format for messaging
+
+

Returns

+
+
String
+
formatted article
+
+
+ +Expand source code + +
def format_article(article):
+    """format article for messaging (using markdown syntax)
+
+    Args:
+        article (dict): article to format for messaging
+
+    Returns:
+        String: formatted article
+    """
+    sourcename = hf.make_markdown_proof(article["source"]["name"])  # make attributes markdownv2 proof
+    headline = hf.make_markdown_proof(article["title"])
+    url = hf.make_markdown_proof(article["url"])
+    formatted_article = f"_{sourcename}_\n*{headline}*\n\n{url}"  # formatting in Markdown syntax
+
+    return formatted_article
+
+
+
+def get_all_news_by_keyword(keyword, from_date='2000-01-01') +
+
+

get all news to keyword

+

Args

+
+
keyword : String
+
keyword for search
+
from_date : String
+
min date for search
+
+

Returns

+

JSON/dict: dict containing articles

+
+ +Expand source code + +
def get_all_news_by_keyword(keyword, from_date="2000-01-01"):
+    """get all news to keyword
+    Args:
+        keyword (String): keyword for search
+        from_date (String): min date for search
+
+    Returns:
+        JSON/dict: dict containing articles
+    """
+    top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date)  # keywords can be combined with OR (e.g. keyword = "bitcoin OR ethereum")
+    if (top_headlines["status"] == "ok"):
+        return top_headlines
+    else:
+        return None
+
+
+
+def get_top_news_by_keyword(keyword) +
+
+

get top news to keyword

+

Args

+
+
keyword : String
+
keyword for search
+
+

Returns

+

JSON/dict: dict containing articles

+
+ +Expand source code + +
def get_top_news_by_keyword(keyword):
+    """get top news to keyword
+    Args:
+        keyword (String): keyword for search
+
+    Returns:
+        JSON/dict: dict containing articles
+    """
+    top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en')  # get top headlines, measured by popularity from NewsApi
+    if (top_headlines["status"] == "ok"):
+        return top_headlines
+    else:
+        return None
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/shares/index.html b/documentation/telegram_bot/shares/index.html new file mode 100644 index 0000000..7707868 --- /dev/null +++ b/documentation/telegram_bot/shares/index.html @@ -0,0 +1,65 @@ + + + + + + +telegram_bot.shares API documentation + + + + + + + + + + + +
+
+
+

Namespace telegram_bot.shares

+
+
+
+
+

Sub-modules

+
+
telegram_bot.shares.share_fetcher
+
+

script for share fetching (by symbols (e.g. AAPL, TSLA etc.))

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/documentation/telegram_bot/shares/share_fetcher.html b/documentation/telegram_bot/shares/share_fetcher.html new file mode 100644 index 0000000..bb0e33c --- /dev/null +++ b/documentation/telegram_bot/shares/share_fetcher.html @@ -0,0 +1,384 @@ + + + + + + +telegram_bot.shares.share_fetcher API documentation + + + + + + + + + + + +
+
+
+

Module telegram_bot.shares.share_fetcher

+
+
+

script for share fetching (by symbols (e.g. AAPL, TSLA etc.))

+
+ +Expand source code + +
"""
+script for share fetching (by symbols (e.g. AAPL, TSLA etc.))
+"""
+__author__ = "Florian Kellermann, Linus Eickhoff"
+__date__ = "10.05.2022"
+__version__ = "1.0.1"
+__license__ = "None"
+
+import telegram_bot.helper_functions as hf
+import investpy
+import pandas
+from currency_converter import CurrencyConverter
+
+
+def get_share_price(str_search_for):
+    """get stock price per share for company name or isin or symbol
+
+    Args:
+        str_search_for (string): search for this string/isin
+
+    Returns: none
+    """
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1, countries=['germany'])
+
+        currency = str(search_result.retrieve_currency()) # retrieve currency from data
+        # should always be Euro because of countries=['germany']
+        
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data()) # stock prices of last few days
+        
+        stock_price = recent_data.iloc[-1]["Close"] # retrieve latest stock price
+        
+        stock_price = round(float(stock_price), 2)
+        
+        str_return =str(stock_price) + " " + str(currency) # return + currency 
+    
+        return str_return
+    
+    except RuntimeError: # if no shares are found for germany (e.g. isin: US.....)
+        try:
+            my_Converter = CurrencyConverter() # need a currency converter
+            
+            search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1)
+
+            currency = str(search_result.retrieve_currency())
+
+            recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+            stock_price = recent_data.iloc[-1]["Close"]
+            
+            #convert stock price from currency to EUR
+            stock_price = my_Converter.convert(float(stock_price), str(currency), 'EUR') 
+            
+            stock_price = round(float(stock_price), 2)
+
+            str_return = str(stock_price) + " EUR"
+
+            return str_return
+
+        except RuntimeError:
+            return "None"
+
+
+def get_share_price_no_currency(str_search_for):
+    """get stock price per share for company name or isin or symbol no currency
+    Args:
+        str_search_for (string): search for this string/isin
+    Returns: none
+    """
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                               countries=['germany'], n_results=1)
+
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+        stock_price = recent_data.iloc[-1]["Close"]
+
+        stock_price = round(float(stock_price), 2)
+
+        return stock_price
+
+    except RuntimeError:
+        my_Converter = CurrencyConverter()
+
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1)
+
+        currency = str(search_result.retrieve_currency())
+
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+        stock_price = recent_data.iloc[-1]["Close"]
+
+        stock_price = my_Converter.convert(float(stock_price), str(currency), 'EUR')
+
+        stock_price = round(float(stock_price), 2)
+
+        str_return = str(stock_price)
+
+        return str_return
+
+
+def get_share_information(str_search_for):
+    search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                           countries=['germany'], n_results=1)
+
+    str_return = "Company: " + search_result.name + "\nSymbol: " + search_result.symbol + "\nCurrent Price/Share: " + get_share_price(str_search_for)
+
+    return str_return
+
+
+def get_share_information_markdown(str_search_for):
+
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                                countries=['germany'], n_results=1)
+    
+    except RuntimeError as e:
+        return hf.make_markdown_proof(f"no shares found for \"{str_search_for}\"") # if no shares are found, make error message markdown proof and return
+
+    except ConnectionError as e:
+        return hf.make_markdown_proof(f"connection not possible. Try again later.") # if no connection, make error message markdown proof and return
+    
+    str_return = f'*{hf.make_markdown_proof(search_result.name)}*\n_{hf.make_markdown_proof(search_result.symbol)}_\nworth: {hf.make_markdown_proof(get_share_price(str_search_for))}'
+    return str_return
+
+
+def get_share_information_simple(str_search_for):
+    search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                           countries=['germany'], n_results=1)
+
+    str_return = search_result.name + "\n" + search_result.symbol + "\nworth: " + get_share_price(str_search_for)
+    return str_return
+
+
+if __name__ == "__main__":
+    print("None")
+
+
+
+
+
+
+
+

Functions

+
+
+def get_share_information(str_search_for) +
+
+
+
+ +Expand source code + +
def get_share_information(str_search_for):
+    search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                           countries=['germany'], n_results=1)
+
+    str_return = "Company: " + search_result.name + "\nSymbol: " + search_result.symbol + "\nCurrent Price/Share: " + get_share_price(str_search_for)
+
+    return str_return
+
+
+
+def get_share_information_markdown(str_search_for) +
+
+
+
+ +Expand source code + +
def get_share_information_markdown(str_search_for):
+
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                                countries=['germany'], n_results=1)
+    
+    except RuntimeError as e:
+        return hf.make_markdown_proof(f"no shares found for \"{str_search_for}\"") # if no shares are found, make error message markdown proof and return
+
+    except ConnectionError as e:
+        return hf.make_markdown_proof(f"connection not possible. Try again later.") # if no connection, make error message markdown proof and return
+    
+    str_return = f'*{hf.make_markdown_proof(search_result.name)}*\n_{hf.make_markdown_proof(search_result.symbol)}_\nworth: {hf.make_markdown_proof(get_share_price(str_search_for))}'
+    return str_return
+
+
+
+def get_share_information_simple(str_search_for) +
+
+
+
+ +Expand source code + +
def get_share_information_simple(str_search_for):
+    search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                           countries=['germany'], n_results=1)
+
+    str_return = search_result.name + "\n" + search_result.symbol + "\nworth: " + get_share_price(str_search_for)
+    return str_return
+
+
+
+def get_share_price(str_search_for) +
+
+

get stock price per share for company name or isin or symbol

+

Args

+
+
str_search_for : string
+
search for this string/isin
+
+

Returns: none

+
+ +Expand source code + +
def get_share_price(str_search_for):
+    """get stock price per share for company name or isin or symbol
+
+    Args:
+        str_search_for (string): search for this string/isin
+
+    Returns: none
+    """
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1, countries=['germany'])
+
+        currency = str(search_result.retrieve_currency()) # retrieve currency from data
+        # should always be Euro because of countries=['germany']
+        
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data()) # stock prices of last few days
+        
+        stock_price = recent_data.iloc[-1]["Close"] # retrieve latest stock price
+        
+        stock_price = round(float(stock_price), 2)
+        
+        str_return =str(stock_price) + " " + str(currency) # return + currency 
+    
+        return str_return
+    
+    except RuntimeError: # if no shares are found for germany (e.g. isin: US.....)
+        try:
+            my_Converter = CurrencyConverter() # need a currency converter
+            
+            search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1)
+
+            currency = str(search_result.retrieve_currency())
+
+            recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+            stock_price = recent_data.iloc[-1]["Close"]
+            
+            #convert stock price from currency to EUR
+            stock_price = my_Converter.convert(float(stock_price), str(currency), 'EUR') 
+            
+            stock_price = round(float(stock_price), 2)
+
+            str_return = str(stock_price) + " EUR"
+
+            return str_return
+
+        except RuntimeError:
+            return "None"
+
+
+
+def get_share_price_no_currency(str_search_for) +
+
+

get stock price per share for company name or isin or symbol no currency

+

Args

+
+
str_search_for : string
+
search for this string/isin
+
+

Returns: none

+
+ +Expand source code + +
def get_share_price_no_currency(str_search_for):
+    """get stock price per share for company name or isin or symbol no currency
+    Args:
+        str_search_for (string): search for this string/isin
+    Returns: none
+    """
+    try:
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
+                                               countries=['germany'], n_results=1)
+
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+        stock_price = recent_data.iloc[-1]["Close"]
+
+        stock_price = round(float(stock_price), 2)
+
+        return stock_price
+
+    except RuntimeError:
+        my_Converter = CurrencyConverter()
+
+        search_result = investpy.search_quotes(text=str_search_for, products=['stocks'], n_results=1)
+
+        currency = str(search_result.retrieve_currency())
+
+        recent_data = pandas.DataFrame(search_result.retrieve_recent_data())
+
+        stock_price = recent_data.iloc[-1]["Close"]
+
+        stock_price = my_Converter.convert(float(stock_price), str(currency), 'EUR')
+
+        stock_price = round(float(stock_price), 2)
+
+        str_return = str(stock_price)
+
+        return str_return
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file From f81d99e1a9561b775b1edafb3e454bfb671676e3 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Thu, 12 May 2022 15:46:29 +0200 Subject: [PATCH 3/3] /update shall also be markdown proof --- telegram_bot/bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 3ba5689..9fecd70 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -230,7 +230,8 @@ def update_for_user(message): if len(share_symbols) != 0: for i in range(len(share_symbols)): my_price = share_fetcher.get_share_price_no_currency(share_symbols[i]) - my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {share_amounts[i]}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR' + amounts = hf.make_markdown_proof(share_amounts[i]) + my_update_message = f'{share_fetcher.get_share_information_markdown(share_symbols[i])}\ncount: {amounts}\nTotal: {hf.make_markdown_proof(round(float(my_price) * float(share_amounts[i]), 2))} EUR' bot.send_message(chat_id=p_user_id, text=my_update_message, parse_mode="MARKDOWNV2") else: send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id)