Reformatting

This commit is contained in:
Administrator 2022-05-11 23:33:48 +02:00
parent 24eb954856
commit 397dd23b8d
21 changed files with 424 additions and 439 deletions

View File

@ -3,6 +3,7 @@
Aktienbot API
## Development
1. Create virtual environment `python -m venv venv env/Scripts/activate`
2. Install requirements `pip install -r api/requirements.txt`
3. Set environment variables (see list below)
@ -10,8 +11,8 @@ Aktienbot API
2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`)
4. Run api `python api/app.py`
## Testing
1. Create virtual environment `python -m venv venv env/Scripts/activate`
2. Install requirements `pip install -r api/requirements.txt`
3. Set environment variables (see list below)
@ -21,6 +22,7 @@ Aktienbot API
5. Run tests: `python -m pytest -v --cov-report term-missing --cov=app`
## Environment variables
```
# Flask secret key
SECRET_KEY=
@ -34,6 +36,7 @@ Aktienbot API
```
## Docker
```
docker run -d \
--name aktienbot_api \
@ -48,4 +51,5 @@ docker run -d \
--restart unless-stopped \
registry.flokaiser.com/aktienbot/api:latest
```
or load environment variables from file by using `--env-file <filename>`

View File

@ -4,21 +4,19 @@ __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin P
__license__ = "GPL 3.0"
__version__ = "1.0.0"
from flask import current_app
from apiflask import APIFlask
from dotenv import load_dotenv
from flask_cors import CORS
from app.blueprints.keyword import keyword_blueprint
from app.blueprints.portfolio import portfolio_blueprint
from app.blueprints.shares import shares_blueprint
from app.blueprints.share_price import share_price_blueprint
from app.blueprints.transactions import transaction_blueprint
from app.blueprints.shares import shares_blueprint
from app.blueprints.telegram import telegram_blueprint
from app.blueprints.transactions import transaction_blueprint
from app.blueprints.user import users_blueprint
from app.helper_functions import hash_password
from app.models import *
from dotenv import load_dotenv
from flask import current_app
from flask_cors import CORS
def create_app(config_filename=None):

View File

@ -4,10 +4,9 @@ __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin P
__license__ = "GPL 3.0"
__version__ = "1.0.0"
from flask import current_app
import jwt
from apiflask import HTTPTokenAuth
from flask import current_app
auth = HTTPTokenAuth()

View File

@ -7,12 +7,11 @@ __version__ = "1.0.0"
import os
from apiflask import APIBlueprint, abort
from app.auth import auth
from app.db import database as db
from app.helper_functions import make_response, get_email_or_abort_401
from app.auth import auth
from app.schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema
from app.models import Keyword
from app.schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema
keyword_blueprint = APIBlueprint('keyword', __name__, url_prefix='/api')
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))

View File

@ -6,9 +6,8 @@ __version__ = "1.0.0"
import pytest
from app import create_app, db
from app.models import User, Transaction, Keyword, Share
from app.helper_functions import hash_password
from app.models import User, Transaction, Keyword, Share
@pytest.fixture(scope='module')

View File

@ -8,6 +8,7 @@ __version__ = "1.0.0"
This file (test_keyword.py) contains the functional tests for the `keyword` blueprint.
"""
import json
from tests.functional.helper_functions import get_token

View File

@ -8,6 +8,7 @@ __version__ = "1.0.0"
This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint.
"""
import json
from tests.functional.helper_functions import get_token

View File

@ -8,6 +8,7 @@ __version__ = "1.0.0"
This file (test_share.py) contains the functional tests for the `share` blueprint.
"""
import json
from tests.functional.helper_functions import get_token

View File

@ -8,6 +8,7 @@ __version__ = "1.0.0"
This file (test_telegram.py) contains the functional tests for the `telegram` blueprint.
"""
import json
from tests.functional.helper_functions import get_token

View File

@ -8,6 +8,7 @@ __version__ = "1.0.0"
This file (test_transaction.py) contains the functional tests for the `transaction` blueprint.
"""
import json
from tests.functional.helper_functions import get_token

View File

@ -3,6 +3,7 @@
Aktienbot telegram bot
## Development
1. Create virtual environment `python -m venv venv`
2. Launch venv: `.\venv\Scripts\activate`
3. Install requirements `pip install -r telegram_bot/requirements.txt`
@ -12,6 +13,7 @@ Aktienbot telegram bot
5. Run api `python telegram_bot/bot.py`
## Environment variables
```
# Telegram bot api key
BOT_API_KEY=
@ -21,6 +23,7 @@ Aktienbot telegram bot
```
## Docker
```
docker run -d \
--name aktienbot_bot \
@ -31,4 +34,5 @@ docker run -d \
--restart unless-stopped \
registry.flokaiser.com/aktienbot/bot:latest
```
or load environment variables from file by using `--env-file <filename>`

View File

@ -9,14 +9,16 @@ __license__ = "None"
# side-dependencies: none
# Work in Progress
import sys
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:
@ -43,7 +45,6 @@ class API_Handler:
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
@ -63,7 +64,6 @@ class API_Handler:
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
@ -87,7 +87,6 @@ class API_Handler:
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
@ -113,7 +112,6 @@ class API_Handler:
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
@ -138,7 +136,6 @@ class API_Handler:
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
@ -169,8 +166,6 @@ class API_Handler:
else:
return self.get_user_keywords(user_id, max_retries - 1)
def set_keyword(self, user_id, keyword):
"""sets the keyword of the user
@ -190,7 +185,6 @@ class API_Handler:
return req.status_code
def delete_keyword(self, user_id, keyword):
"""deletes the keyword of the user
@ -210,7 +204,6 @@ class API_Handler:
return req.status_code
def get_user_shares(self, user_id, max_retries=10):
"""gets the shares of the user
@ -241,7 +234,6 @@ class API_Handler:
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
@ -258,10 +250,10 @@ class API_Handler:
"""
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."
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
@ -280,7 +272,6 @@ class API_Handler:
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
@ -307,7 +298,6 @@ class API_Handler:
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
@ -328,11 +318,11 @@ class API_Handler:
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
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
@ -380,7 +370,6 @@ class API_Handler:
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

View File

@ -1,4 +1,3 @@
"""
script for telegram bot and its functions
"""
@ -14,24 +13,21 @@ __license__ = "None"
# 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
import sys
import logging
import re
from dotenv import load_dotenv
from telebot import types
import helper_functions as hf
import news.news_fetcher as news
import shares.share_fetcher as share_fetcher
import helper_functions as hf
import datetime as dt
from telebot import types
from dotenv import load_dotenv
from api_handling.api_handler import API_Handler
load_dotenv(dotenv_path='.env') # load environment variables
bot_version = "2.0.1" # version of bot
@ -42,9 +38,9 @@ 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'
@ -61,7 +57,6 @@ def send_start(message):
@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'
@ -75,7 +70,6 @@ def send_version(message):
@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'
@ -84,12 +78,12 @@ def send_help(message):
: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.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'
@ -110,7 +104,6 @@ def send_all_users(message):
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']
@ -122,7 +115,6 @@ def send_all_users(message):
@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'
@ -141,6 +133,7 @@ def set_admin(message):
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
@ -190,7 +183,6 @@ def send_user(message):
@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'
@ -206,7 +198,6 @@ def send_id(message):
# 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
@ -220,7 +211,6 @@ def send_status(message):
@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
@ -248,7 +238,6 @@ def update_for_user(message):
def send_to_user(pText, pUser_id):
""" Send message to user
:type pText: string
:param pText: Text to send to user
@ -265,7 +254,6 @@ def send_to_user(pText, pUser_id):
@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'
@ -279,6 +267,7 @@ def send_share_update(message):
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")
@ -286,7 +275,6 @@ def send_share_price(message):
@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'
@ -373,6 +361,7 @@ def add_keyword(message):
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
@ -397,6 +386,7 @@ def remove_keyword(message):
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()
@ -503,7 +493,8 @@ def set_new_transaction(message):
: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.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)
@ -609,7 +600,6 @@ def send_shares(message):
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
@ -624,8 +614,8 @@ def set_new_interval(message):
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):
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
@ -643,7 +633,6 @@ def set_new_interval_step(message):
@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
@ -661,7 +650,6 @@ 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:
@ -679,7 +667,6 @@ def query_text(inline_query):
def main_loop():
""" Start bot
:raises: none
@ -687,6 +674,7 @@ def main_loop():
"""
bot.infinity_polling()
if __name__ == '__main__':
try:
main_loop()

View File

@ -6,17 +6,18 @@ __date__ = "10.05.2022"
__version__ = "1.0.2"
__license__ = "None"
from dotenv import load_dotenv
import news.news_fetcher as news_fetcher
import time
import os
from bot import bot
import sys
from apscheduler.schedulers.background import BackgroundScheduler
from api_handling.api_handler import API_Handler
import shares.share_fetcher as share_fetcher
import helper_functions as hf
import time
from apscheduler.schedulers.background import BackgroundScheduler
from dotenv import load_dotenv
import helper_functions as hf
import news.news_fetcher as news_fetcher
import shares.share_fetcher as share_fetcher
from api_handling.api_handler import API_Handler
from bot import bot
'''
* * * * * code
@ -35,6 +36,7 @@ user_crontab = []
load_dotenv(dotenv_path='.env')
def start_updater():
""" starting function for regularly sending updates
:raises: none
@ -46,7 +48,6 @@ def start_updater():
my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD")))
update_crontab(my_handler)
@ -76,8 +77,8 @@ def update_crontab(p_my_handler):
user_crontab.append(str(element["cron"]))
except:
user_ids.pop()
except: continue
except:
continue
print(user_ids)
@ -87,7 +88,6 @@ def update_crontab(p_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
@ -115,8 +115,8 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler):
time.sleep(600)
my_scheduler.shutdown()
def update_for_user(p_user_id, p_my_handler):
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
@ -153,13 +153,11 @@ def update_for_user(p_user_id, p_my_handler):
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
@ -178,9 +176,7 @@ def update_for_user(p_user_id, p_my_handler):
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
@ -201,7 +197,6 @@ def send_to_user(pText, pUser_id , md_mode = False):
bot.send_message(chat_id=pUser_id, text=pText)
if __name__ == "__main__":
try:
start_updater()

View File

@ -6,6 +6,7 @@ __date__ = "10.05.2022"
__version__ = "1.0.0"
__license__ = "None"
def contains_markdownv1_symbols(text):
""" checks if text contains markdown symbols
:type text: string
@ -57,7 +58,6 @@ def make_markdown_proof(text): # used to avoid errors related to markdown parsem
text = text.replace("$", "\\$")
text = text.replace("%", "\\%")
return text

View File

@ -6,14 +6,13 @@ __date__ = "26.04.2022"
__version__ = "1.0.0"
__license__ = "None"
import sys
import os
import requests
import sys
import helper_functions as hf
from newsapi import NewsApiClient
import requests
from dotenv import load_dotenv
from newsapi import NewsApiClient
load_dotenv() # loads environment vars
@ -29,7 +28,8 @@ try:
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")
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"):

View File

@ -6,10 +6,11 @@ __date__ = "10.05.2022"
__version__ = "1.0.1"
__license__ = "None"
import helper_functions as hf
import investpy
import pandas
from currency_converter import CurrencyConverter
import helper_functions as hf
def get_share_price(str_search_for):
"""get stock price per share for company name or isin or symbol
@ -57,6 +58,7 @@ def get_share_price(str_search_for):
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:
@ -67,7 +69,6 @@ def get_share_price_no_currency(str_search_for):
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"]
@ -95,6 +96,7 @@ def get_share_price_no_currency(str_search_for):
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)
@ -103,6 +105,7 @@ def get_share_information(str_search_for):
return str_return
def get_share_information_markdown(str_search_for):
search_result = investpy.search_quotes(text=str_search_for, products=['stocks'],
countries=['germany'], n_results=1)
@ -110,6 +113,7 @@ def get_share_information_markdown(str_search_for):
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)
@ -117,5 +121,6 @@ def get_share_information_simple(str_search_for):
str_return = search_result.name + "\n" + search_result.symbol + "\nworth: " + get_share_price(str_search_for)
return str_return
if __name__ == "__main__":
print("None")