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

@ -6,17 +6,19 @@ __date__ = "10.05.2022"
__version__ = "1.0.2"
__license__ = "None"
#side-dependencies: none
#Work in Progress
# 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
@ -107,12 +106,11 @@ class API_Handler:
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):
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
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
@ -132,12 +130,11 @@ class API_Handler:
with r.Session() as s:
headers = {'Authorization': 'Bearer ' + self.token}
req = s.get(self.db_adress + "/users", headers=headers)
if(req.status_code == 200):
if (req.status_code == 200):
return req.json()["data"]
else:
return self.get_all_users(max_retries-1)
return self.get_all_users(max_retries - 1)
def get_user_keywords(self, user_id, max_retries=10):
"""gets the keywords of the user
@ -159,7 +156,7 @@ class API_Handler:
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):
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"])
@ -167,9 +164,7 @@ class API_Handler:
return keywords # will be empty if no keywords are set
else:
return self.get_user_keywords(user_id, max_retries-1)
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
@ -230,7 +223,7 @@ class API_Handler:
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):
if (req.status_code == 200):
shares_json = req.json()["data"]
shares = []
for share in shares_json:
@ -239,8 +232,7 @@ class API_Handler:
return shares
else:
return self.get_user_shares(user_id, max_retries-1)
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
@ -305,8 +296,7 @@ class API_Handler:
transactions_dict = req.json()["data"]
return transactions_dict
else:
return self.get_user_transactions(user_id, max_retries-1)
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
@ -356,7 +346,7 @@ class API_Handler:
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)
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
@ -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
@ -396,7 +385,7 @@ class API_Handler:
"""
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)
req = s.put(self.db_adress + "/user/setAdmin", json={"admin": is_admin, "email": str(email)}, headers=headers)
return req.status_code
@ -405,11 +394,11 @@ if __name__ == "__main__": # editable, just for basic on the go testing of new f
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)
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)
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")

View File

@ -1,4 +1,3 @@
"""
script for telegram bot and its functions
"""
@ -14,37 +13,34 @@ __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
#create api handler
# 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'
@ -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'
@ -101,7 +95,7 @@ def send_all_users(message):
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
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
@ -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'
@ -134,13 +126,14 @@ def set_admin(message):
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
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
@ -158,7 +151,7 @@ def set_admin_step(message):
status = api_handler.set_admin(email, is_admin) # set admin in db
if(status == 200):
if (status == 200):
bot.reply_to(message, "Admin rights set")
else:
@ -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'
@ -203,10 +195,9 @@ def send_id(message):
bot.reply_to(message, answer)
#function that can be used to ensure that the bot is online and running
# 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
@ -230,13 +220,13 @@ def update_for_user(message):
my_portfolio = p_my_handler.get_user_portfolio(p_user_id)
for element in my_portfolio:
if element["count"] != '' and element["isin"]!= '':
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)
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)):
@ -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()
@ -492,7 +482,7 @@ def remove_share_step(message):
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
@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
@ -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
@ -108,15 +108,15 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler):
for i in range(len(p_user_ids)):
cron_split = p_user_crontab[i].split(" ")
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.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 )
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
@ -135,13 +135,13 @@ def update_for_user(p_user_id, p_my_handler):
my_portfolio = p_my_handler.get_user_portfolio(p_user_id)
for element in my_portfolio:
if element["count"] != '' and element["isin"]!= '':
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)
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)
@ -153,16 +153,14 @@ 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
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
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"]
@ -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):
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"):
@ -42,7 +42,7 @@ def get_all_news_by_keyword(keyword, from_date="2000-01-01"):
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"):
if (top_headlines["status"] == "ok"):
return top_headlines
else:
return None
@ -57,7 +57,7 @@ def get_top_news_by_keyword(keyword):
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"):
if (top_headlines["status"] == "ok"):
return top_headlines
else:
return None

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
@ -30,7 +31,7 @@ def get_share_price(str_search_for):
stock_price = round(float(stock_price), 2)
str_return =str(stock_price) + " " + str(currency)
str_return = str(stock_price) + " " + str(currency)
return str_return
@ -50,13 +51,14 @@ def get_share_price(str_search_for):
stock_price = round(float(stock_price), 2)
str_return =str(stock_price) + " EUR"
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:
@ -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"]
@ -91,10 +92,11 @@ def get_share_price_no_currency(str_search_for):
stock_price = round(float(stock_price), 2)
str_return =str(stock_price)
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)
@ -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,12 +113,14 @@ 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)
str_return = search_result.name + "\n" +search_result.symbol + "\nworth: " + get_share_price(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")