1
0
mirror of https://gitlab.com/simple-stock-bots/simple-stock-bot.git synced 2026-06-03 21:00:26 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Anson d74ce33fc7 fix pipeline? 2023-09-03 13:00:26 -06:00
Anson 4002f9f744 fix pipeline? 2023-09-03 13:00:02 -06:00
Anson b9725e5c14 Merge branch 'master' into 101-combine-discord-and-telegram-bots-into-one-repo-and-deploy-with-docker-compose 2023-09-03 12:56:28 -06:00
Anson e4280d808b add discord bot 2023-09-03 12:46:18 -06:00
12 changed files with 178 additions and 184 deletions
+20 -3
View File
@@ -1,3 +1,20 @@
FROM python:3.11 FROM python:3.11-buster AS builder
COPY . .
COPY requirements.txt /requirements.txt
RUN pip install --user -r requirements.txt
FROM python:3.11-slim
ENV MPLBACKEND=Agg
COPY --from=builder /root/.local /root/.local
RUN pip install --no-cache-dir black
ENV TELEGRAM=TOKEN
ENV MARKETDATA=TOKEN
COPY . .
# CMD [ "python", "./bot.py" ]
+22 -35
View File
@@ -1,39 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// README at: https://github.com/devcontainers/templates/tree/main/src/python // https://github.com/microsoft/vscode-dev-containers/tree/v0.191.0/containers/docker-existing-dockerfile
{ {
"name": "Python 3", "name": "DockerDev",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Sets the run context to one level up instead of the .devcontainer folder.
// "image": "mcr.microsoft.com/devcontainers/python:1-3-bookworm", "context": "..",
"build": { // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "Dockerfile" "dockerFile": "Dockerfile",
}, // Set *default* container specific settings.json values on container create.
"features": { "settings": {},
"ghcr.io/devcontainers-contrib/features/black:2": {}, // Add the IDs of extensions you want installed when the container is created.
"ghcr.io/devcontainers-contrib/features/mypy:2": {}, "extensions": [
"ghcr.io/devcontainers-contrib/features/pylint:2": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python", "ms-python.python",
"ms-python.black-formatter", "ms-azuretools.vscode-docker"
"ms-python.flake8",
"ms-python.vscode-pylance",
"ms-python.isort",
"charliermarsh.ruff"
] ]
} // Use 'forwardPorts' to make a list of ports inside the container available locally.
}, // "forwardPorts": [],
"postCreateCommand": "pip3 install --user -r dev-reqs.txt" // Uncomment the next line to run commands after the container is created - for example installing curl.
// Features to add to the dev container. More info: https://containers.dev/features. // "postCreateCommand": "apt-get update && apt-get install -y curl",
// "features": {}, // Uncomment when using a ptrace-based debugger like C++, Go, and Rust
// Use 'forwardPorts' to make a list of ports inside the container available locally. // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// "forwardPorts": [], // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
// Use 'postCreateCommand' to run commands after the container is created. // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
// "postCreateCommand": "pip3 install --user -r requirements.txt", // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
// Configure tool-specific properties. // "remoteUser": "vscode"
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
} }
+12
View File
@@ -0,0 +1,12 @@
{
"configurations": [
{
"name": "Telegram Bot",
"type": "python",
"request": "launch",
"program": "bot.py",
"console": "integratedTerminal",
"python": "python3.11"
}
]
}
+3 -6
View File
@@ -1,8 +1,5 @@
{ {
"editor.formatOnSave": true, "python.formatting.provider": "black",
"editor.formatOnPaste": true, "python.linting.mypyEnabled": true,
"editor.formatOnSaveMode": "modificationsIfAvailable", "python.linting.flake8Enabled": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
}
} }
+8 -14
View File
@@ -103,7 +103,7 @@ class MarketData:
) )
status.raise_for_status() status.raise_for_status()
except r.HTTPError: except r.HTTPError:
return f"API returned an HTTP error code {status.status_code} in {status.elapsed.total_seconds()} seconds." return f"API returned an HTTP error code {status.status_code} in {status.elapsed.total_seconds()} Seconds."
except r.Timeout: except r.Timeout:
return "API timed out before it was able to give status. This is likely due to a surge in usage or a complete outage." return "API timed out before it was able to give status. This is likely due to a surge in usage or a complete outage."
@@ -111,7 +111,7 @@ class MarketData:
if statusJSON["status"] == "ok": if statusJSON["status"] == "ok":
return ( return (
f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} seconds." f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} Seconds."
) )
else: else:
return f"MarketData.app is currently reporting the following status: {statusJSON['status']}" return f"MarketData.app is currently reporting the following status: {statusJSON['status']}"
@@ -131,11 +131,7 @@ class MarketData:
if quoteResp := self.get(f"stocks/quotes/{symbol}/"): if quoteResp := self.get(f"stocks/quotes/{symbol}/"):
price = round(quoteResp["last"][0], 2) price = round(quoteResp["last"][0], 2)
changePercent = round(quoteResp["changepct"][0], 2)
try:
changePercent = round(quoteResp["changepct"][0], 2)
except TypeError:
return f"The price of {symbol} is {price}"
message = f"The current price of {symbol.name} is ${price} and " message = f"The current price of {symbol.name} is ${price} and "
@@ -152,13 +148,11 @@ class MarketData:
def spark_reply(self, symbol: Stock) -> str: def spark_reply(self, symbol: Stock) -> str:
if quoteResp := self.get(f"stocks/quotes/{symbol}/"): if quoteResp := self.get(f"stocks/quotes/{symbol}/"):
try: changePercent = round(quoteResp["changepct"][0], 2)
changePercent = round(quoteResp["changepct"][0], 2) return f"`{symbol.tag}`: {changePercent}%"
return f"`{symbol.tag}`: {changePercent}%" else:
except TypeError: logging.warning(f"{symbol} did not have 'changepct' field.")
pass return f"`{symbol.tag}`"
return f"`{symbol.tag}`"
def intra_reply(self, symbol: Stock) -> pd.DataFrame: def intra_reply(self, symbol: Stock) -> pd.DataFrame:
"""Returns price data for a symbol of the past month up until the previous trading days close. """Returns price data for a symbol of the past month up until the previous trading days close.
+2 -2
View File
@@ -77,10 +77,10 @@ class cg_Crypto:
try: try:
status.raise_for_status() status.raise_for_status()
return ( return (
f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} seconds." f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} Seconds."
) )
except r.HTTPError: except r.HTTPError:
return f"CoinGecko API returned an error code {status.status_code} in {status.elapsed.total_seconds()} seconds." return f"CoinGecko API returned an error code {status.status_code} in {status.elapsed.total_seconds()} Seconds."
def price_reply(self, coin: Coin) -> str: def price_reply(self, coin: Coin) -> str:
"""Returns current market price or after hours if its available for a given coin symbol. """Returns current market price or after hours if its available for a given coin symbol.
+4 -6
View File
@@ -123,8 +123,7 @@ class Router:
Each tuple contains: (Symbol, Issue Name). Each tuple contains: (Symbol, Issue Name).
""" """
# df = pd.concat([self.stock.symbol_list, self.crypto.symbol_list]) df = pd.concat([self.stock.symbol_list, self.crypto.symbol_list])
df = self.crypto.symbol_list
df = df[df["description"].str.contains(search, regex=False, case=False)].sort_values( df = df[df["description"].str.contains(search, regex=False, case=False)].sort_values(
by="type_id", key=lambda x: x.str.len() by="type_id", key=lambda x: x.str.len()
@@ -339,8 +338,8 @@ class Router:
reply += self.spark_reply(self.find_symbols(t))[0] + "\n" reply += self.spark_reply(self.find_symbols(t))[0] + "\n"
if coins: if coins:
reply += "\n\n🦎Trending on CoinGecko:\n`" reply += "\n\n🦎Trending Crypto:\n`"
reply += "" * len("Trending on CoinGecko:") + "`\n" reply += "" * len("Trending Crypto:") + "`\n"
for coin in coins: for coin in coins:
reply += coin + "\n" reply += coin + "\n"
@@ -354,8 +353,7 @@ class Router:
return "Trending data is not currently available." return "Trending data is not currently available."
def random_pick(self) -> str: def random_pick(self) -> str:
# choice = random.choice(list(self.stock.symbol_list["description"]) + list(self.crypto.symbol_list["description"])) choice = random.choice(list(self.stock.symbol_list["description"]) + list(self.crypto.symbol_list["description"]))
choice = random.choice(list(self.crypto.symbol_list["description"]))
hold = (datetime.date.today() + datetime.timedelta(random.randint(1, 365))).strftime("%b %d, %Y") hold = (datetime.date.today() + datetime.timedelta(random.randint(1, 365))).strftime("%b %d, %Y")
return f"{choice}\nBuy and hold until: {hold}" return f"{choice}\nBuy and hold until: {hold}"
+5 -6
View File
@@ -1,9 +1,8 @@
-r telegram/requirements.txt -r telegram/requirements.txt
black==23.7.0 black==23.3.0
flake8==6.1.0 flake8==5.0.4
Flake8-pyproject==1.2.3 Flake8-pyproject==1.2.3
pylama==8.4.1 pylama==8.4.1
mypy==1.5.1 mypy==1.2.0
types-cachetools==5.3.0.6 types-cachetools==5.3.0.5
types-pytz==2023.3.0.1 types-pytz==2023.3.0.0
ruff==0.0.287
+1 -1
View File
@@ -42,7 +42,7 @@ async def status(ctx: commands):
message = "" message = ""
try: try:
message = "Contact MisterBiggs#0465 if you need help.\n" message = "Contact MisterBiggs#0465 if you need help.\n"
message += s.status(f"Bot recieved your message in: {bot.latency*10:.4f} seconds") + "\n" message += s.status(f"Bot recieved your message in: {bot.latency*1000:.4f}ms") + "\n"
except Exception as ex: except Exception as ex:
logging.critical(ex) logging.critical(ex)
+1 -4
View File
@@ -5,7 +5,4 @@ line-length = 130
max-line-length = 130 max-line-length = 130
[tool.pycodestyle] [tool.pycodestyle]
max_line_length = 130 max_line_length = 130
[tool.ruff]
line-length = 130
+93 -100
View File
@@ -18,15 +18,14 @@ from telegram import (
LabeledPrice, LabeledPrice,
Update, Update,
) )
from telegram.ext import ( from telegram.ext import (
Application, CallbackContext,
CommandHandler, CommandHandler,
Filters,
InlineQueryHandler, InlineQueryHandler,
PreCheckoutQueryHandler,
MessageHandler, MessageHandler,
filters, PreCheckoutQueryHandler,
ContextTypes, Updater,
) )
from common.symbol_router import Router from common.symbol_router import Router
@@ -34,10 +33,6 @@ from T_info import T_info
# Enable logging # Enable logging
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO) logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
TELEGRAM_TOKEN = os.environ["TELEGRAM"] TELEGRAM_TOKEN = os.environ["TELEGRAM"]
@@ -55,58 +50,58 @@ t = T_info()
log.info("Bot script started.") log.info("Bot script started.")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): def start(update: Update, context: CallbackContext):
"""Send help text when the command /start is issued.""" """Send help text when the command /start is issued."""
log.info(f"Start command ran by {update.message.chat.username}") log.info(f"Start command ran by {update.message.chat.username}")
await update.message.reply_text( update.message.reply_text(
text=t.help_text, text=t.help_text,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def help(update: Update, context: ContextTypes.DEFAULT_TYPE): def help(update: Update, context: CallbackContext):
"""Send help text when the command /help is issued.""" """Send help text when the command /help is issued."""
log.info(f"Help command ran by {update.message.chat.username}") log.info(f"Help command ran by {update.message.chat.username}")
await update.message.reply_text( update.message.reply_text(
text=t.help_text, text=t.help_text,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def license(update: Update, context: ContextTypes.DEFAULT_TYPE): def license(update: Update, context: CallbackContext):
"""Send bots license when the /license command is issued.""" """Send bots license when the /license command is issued."""
log.info(f"License command ran by {update.message.chat.username}") log.info(f"License command ran by {update.message.chat.username}")
await update.message.reply_text( update.message.reply_text(
text=t.license, text=t.license,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE): def status(update: Update, context: CallbackContext):
"""Gather status of bot and dependant services and return important status updates.""" """Gather status of bot and dependant services and return important status updates."""
log.warning(f"Status command ran by {update.message.chat.username}") log.warning(f"Status command ran by {update.message.chat.username}")
bot_resp_time = datetime.datetime.now(update.message.date.tzinfo) - update.message.date bot_resp_time = datetime.datetime.now(update.message.date.tzinfo) - update.message.date
bot_status = s.status(f"It took {bot_resp_time.total_seconds()} seconds for the bot to get your message.") bot_status = s.status(f"It took {bot_resp_time.total_seconds()} seconds for the bot to get your message.")
await update.message.reply_text( update.message.reply_text(
text=bot_status, text=bot_status,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
) )
async def donate(update: Update, context: ContextTypes.DEFAULT_TYPE): def donate(update: Update, context: CallbackContext):
"""Sets up donation.""" """Sets up donation."""
log.info(f"Donate command ran by {update.message.chat.username}") log.info(f"Donate command ran by {update.message.chat.username}")
chat_id = update.message.chat_id chat_id = update.message.chat_id
if update.message.text.strip() == "/donate" or "/donate@" in update.message.text: if update.message.text.strip() == "/donate" or "/donate@" in update.message.text:
await update.message.reply_text( update.message.reply_text(
text=t.donate_text, text=t.donate_text,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
amount = 1.0 amount = 1.0
@@ -116,11 +111,11 @@ async def donate(update: Update, context: ContextTypes.DEFAULT_TYPE):
try: try:
price = int(amount * 100) price = int(amount * 100)
except ValueError: except ValueError:
await update.message.reply_text(f"{amount} is not a valid donation amount or number.") update.message.reply_text(f"{amount} is not a valid donation amount or number.")
return return
log.info(f"Donation amount: {price} by {update.message.chat.username}") log.info(f"Donation amount: {price} by {update.message.chat.username}")
await context.bot.send_invoice( context.bot.send_invoice(
chat_id=chat_id, chat_id=chat_id,
title="Simple Stock Bot Donation", title="Simple Stock Bot Donation",
description=f"Simple Stock Bot Donation of ${amount} by {update.message.chat.username}", description=f"Simple Stock Bot Donation of ${amount} by {update.message.chat.username}",
@@ -136,27 +131,27 @@ async def donate(update: Update, context: ContextTypes.DEFAULT_TYPE):
) )
async def precheckout_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): def precheckout_callback(update: Update, context: CallbackContext):
"""Approves donation""" """Approves donation"""
log.info("precheckout_callback queried") log.info("precheckout_callback queried")
query = update.pre_checkout_query query = update.pre_checkout_query
await query.answer(ok=True) query.answer(ok=True)
# I dont think I need to check since its only donations. # I dont think I need to check since its only donations.
# if query.invoice_payload == "simple-stock-bot": # if query.invoice_payload == "simple-stock-bot":
# # answer False pre_checkout_query # # answer False pre_checkout_query
# await query.answer(ok=True) # query.answer(ok=True)
# else: # else:
# await query.answer(ok=False, error_message="Something went wrong...") # query.answer(ok=False, error_message="Something went wrong...")
async def successful_payment_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): def successful_payment_callback(update: Update, context: CallbackContext):
"""Thanks user for donation""" """Thanks user for donation"""
log.info("Successful payment!") log.info("Successful payment!")
await update.message.reply_text("Thank you for your donation! It goes a long way to keeping the bot free!") update.message.reply_text("Thank you for your donation! It goes a long way to keeping the bot free!")
async def symbol_detect_image(update: Update, context: ContextTypes.DEFAULT_TYPE): def symbol_detect_image(update: Update, context: CallbackContext):
""" """
Makes image captions into text then passes the `update` and `context` Makes image captions into text then passes the `update` and `context`
to symbol detect so that it can reply cashtags in image captions. to symbol detect so that it can reply cashtags in image captions.
@@ -164,12 +159,12 @@ async def symbol_detect_image(update: Update, context: ContextTypes.DEFAULT_TYPE
try: try:
if update.message.caption: if update.message.caption:
update.message.text = update.message.caption update.message.text = update.message.caption
await symbol_detect(update, context) symbol_detect(update, context)
except AttributeError: except AttributeError:
return return
async def symbol_detect(update: Update, context: ContextTypes.DEFAULT_TYPE): def symbol_detect(update: Update, context: CallbackContext):
""" """
Runs on any message that doesn't have a command and searches for cashtags, Runs on any message that doesn't have a command and searches for cashtags,
then returns the prices of any symbols found. then returns the prices of any symbols found.
@@ -187,18 +182,18 @@ async def symbol_detect(update: Update, context: ContextTypes.DEFAULT_TYPE):
return return
if symbols: if symbols:
# Let user know bot is working # Let user know bot is working
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.TYPING) context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
log.info(f"Symbols found: {symbols}") log.info(f"Symbols found: {symbols}")
for reply in s.price_reply(symbols): for reply in s.price_reply(symbols):
await update.message.reply_text( update.message.reply_text(
text=reply, text=reply,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def intra(update: Update, context: ContextTypes.DEFAULT_TYPE): def intra(update: Update, context: CallbackContext):
"""returns a chart of intraday data for a symbol""" """returns a chart of intraday data for a symbol"""
log.info(f"Intra command ran by {update.message.chat.username}") log.info(f"Intra command ran by {update.message.chat.username}")
@@ -206,7 +201,7 @@ async def intra(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.message.chat_id chat_id = update.message.chat_id
if message.strip().split("@")[0] == "/intra": if message.strip().split("@")[0] == "/intra":
await update.message.reply_text( update.message.reply_text(
"This command returns a chart of the stocks movement since the most recent market open.\nExample: /intra $tsla" "This command returns a chart of the stocks movement since the most recent market open.\nExample: /intra $tsla"
) )
return return
@@ -217,19 +212,19 @@ async def intra(update: Update, context: ContextTypes.DEFAULT_TYPE):
if len(symbols): if len(symbols):
symbol = symbols[0] symbol = symbols[0]
else: else:
await update.message.reply_text("No symbols or coins found.") update.message.reply_text("No symbols or coins found.")
return return
df = s.intra_reply(symbol) df = s.intra_reply(symbol)
if df.empty: if df.empty:
await update.message.reply_text( update.message.reply_text(
text="Invalid symbol please see `/help` for usage details.", text="Invalid symbol please see `/help` for usage details.",
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
return return
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.UPLOAD_PHOTO) context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO)
buf = io.BytesIO() buf = io.BytesIO()
mpf.plot( mpf.plot(
@@ -242,17 +237,17 @@ async def intra(update: Update, context: ContextTypes.DEFAULT_TYPE):
) )
buf.seek(0) buf.seek(0)
await update.message.reply_photo( update.message.reply_photo(
photo=buf, photo=buf,
caption=f"\nIntraday chart for {symbol.name} from {df.first_valid_index().strftime('%d %b at %H:%M')} to" caption=f"\nIntraday chart for {symbol.name} from {df.first_valid_index().strftime('%d %b at %H:%M')} to"
+ f" {df.last_valid_index().strftime('%d %b at %H:%M %Z')}" + f" {df.last_valid_index().strftime('%d %b at %H:%M %Z')}"
+ f"\n\n{s.price_reply([symbol])[0]}", + f"\n\n{s.price_reply([symbol])[0]}",
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def chart(update: Update, context: ContextTypes.DEFAULT_TYPE): def chart(update: Update, context: CallbackContext):
"""returns a chart of the past month of data for a symbol""" """returns a chart of the past month of data for a symbol"""
log.info(f"Chart command ran by {update.message.chat.username}") log.info(f"Chart command ran by {update.message.chat.username}")
@@ -260,7 +255,7 @@ async def chart(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.message.chat_id chat_id = update.message.chat_id
if message.strip().split("@")[0] == "/chart": if message.strip().split("@")[0] == "/chart":
await update.message.reply_text( update.message.reply_text(
"This command returns a chart of the stocks movement for the past month.\nExample: /chart $tsla" "This command returns a chart of the stocks movement for the past month.\nExample: /chart $tsla"
) )
return return
@@ -270,18 +265,18 @@ async def chart(update: Update, context: ContextTypes.DEFAULT_TYPE):
if len(symbols): if len(symbols):
symbol = symbols[0] symbol = symbols[0]
else: else:
await update.message.reply_text("No symbols or coins found.") update.message.reply_text("No symbols or coins found.")
return return
df = s.chart_reply(symbol) df = s.chart_reply(symbol)
if df.empty: if df.empty:
await update.message.reply_text( update.message.reply_text(
text="Invalid symbol please see `/help` for usage details.", text="Invalid symbol please see `/help` for usage details.",
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
return return
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.UPLOAD_PHOTO) context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO)
buf = io.BytesIO() buf = io.BytesIO()
mpf.plot( mpf.plot(
@@ -294,42 +289,39 @@ async def chart(update: Update, context: ContextTypes.DEFAULT_TYPE):
) )
buf.seek(0) buf.seek(0)
await update.message.reply_photo( update.message.reply_photo(
photo=buf, photo=buf,
caption=f"\n1 Month chart for {symbol.name} from {df.first_valid_index().strftime('%d, %b %Y')}" caption=f"\n1 Month chart for {symbol.name} from {df.first_valid_index().strftime('%d, %b %Y')}"
+ f" to {df.last_valid_index().strftime('%d, %b %Y')}\n\n{s.price_reply([symbol])[0]}", + f" to {df.last_valid_index().strftime('%d, %b %Y')}\n\n{s.price_reply([symbol])[0]}",
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def trending(update: Update, context: ContextTypes.DEFAULT_TYPE): def trending(update: Update, context: CallbackContext):
"""returns currently trending symbols and how much they've moved in the past trading day.""" """returns currently trending symbols and how much they've moved in the past trading day."""
log.info(f"Trending command ran by {update.message.chat.username}") log.info(f"Trending command ran by {update.message.chat.username}")
chat_id = update.message.chat_id chat_id = update.message.chat_id
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.TYPING) context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
trending_list = s.trending() trending_list = s.trending()
await update.message.reply_text( update.message.reply_text(
text=trending_list, text=trending_list,
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: def inline_query(update: Update, context: CallbackContext):
""" """
Handles inline query. Searches by looking if query is contained Handles inline query. Searches by looking if query is contained
in the symbol and returns matches in alphabetical order. in the symbol and returns matches in alphabetical order.
""" """
# info(f"Inline command ran by {update.message.chat.username}")
if not update.inline_query.query: log.info(f"Query: {update.inline_query.query}")
return
print(f"Query: {update.inline_query.query}")
ignored_queries = {"$", "$$", " ", ""} ignored_queries = {"$", "$$", " ", ""}
@@ -339,14 +331,12 @@ async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
in any chat or direct message to search for the stock bots full list of stock and crypto symbols and return the price. in any chat or direct message to search for the stock bots full list of stock and crypto symbols and return the price.
""" """
await update.inline_query.answer( update.inline_query.answer(
[ [
InlineQueryResultArticle( InlineQueryResultArticle(
str(uuid4()), str(uuid4()),
title="Please enter a query. It can be a ticker or a name of a company.", title="Please enter a query. It can be a ticker or a name of a company.",
input_message_content=InputTextMessageContent( input_message_content=InputTextMessageContent(default_message, parse_mode=telegram.ParseMode.MARKDOWN),
default_message, parse_mode=telegram.constants.ParseMode.MARKDOWN
),
) )
] ]
) )
@@ -359,31 +349,29 @@ async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
InlineQueryResultArticle( InlineQueryResultArticle(
str(uuid4()), str(uuid4()),
title=row["description"], title=row["description"],
input_message_content=InputTextMessageContent( input_message_content=InputTextMessageContent(row["price_reply"], parse_mode=telegram.ParseMode.MARKDOWN),
row["price_reply"], parse_mode=telegram.constants.ParseMode.MARKDOWN
),
) )
) )
if len(results) == 5: if len(results) == 5:
await update.inline_query.answer(results, cache_time=60 * 60) update.inline_query.answer(results, cache_time=60 * 60)
log.info("Inline Command was successful") log.info("Inline Command was successful")
return return
await update.inline_query.answer(results) update.inline_query.answer(results)
async def rand_pick(update: Update, context: ContextTypes.DEFAULT_TYPE): def rand_pick(update: Update, context: CallbackContext):
"""For the gamblers. Returns a random symbol to buy and a sell date""" """For the gamblers. Returns a random symbol to buy and a sell date"""
log.info(f"Someone is gambling! Random_pick command ran by {update.message.chat.username}") log.info(f"Someone is gambling! Random_pick command ran by {update.message.chat.username}")
await update.message.reply_text( update.message.reply_text(
text=s.random_pick(), text=s.random_pick(),
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
disable_notification=True, disable_notification=True,
) )
async def error(update: Update, context: ContextTypes.DEFAULT_TYPE): def error(update: Update, context: CallbackContext):
"""Log Errors caused by Updates.""" """Log Errors caused by Updates."""
log.warning('Update "%s" caused error "%s"', update, error) log.warning('Update "%s" caused error "%s"', update, error)
@@ -402,9 +390,9 @@ async def error(update: Update, context: ContextTypes.DEFAULT_TYPE):
f"\t{html.escape(tb_string)}" f"\t{html.escape(tb_string)}"
) )
await update.message.reply_text( update.message.reply_text(
text=f"An error has occured. Please inform @MisterBiggs if the error persists. Error Code: `{err_code}`", text=f"An error has occured. Please inform @MisterBiggs if the error persists. Error Code: `{err_code}`",
parse_mode=telegram.constants.ParseMode.MARKDOWN, parse_mode=telegram.ParseMode.MARKDOWN,
) )
else: else:
log.warning("No message to send to user.") log.warning("No message to send to user.")
@@ -414,43 +402,48 @@ async def error(update: Update, context: ContextTypes.DEFAULT_TYPE):
def main(): def main():
"""Start the context.bot.""" """Start the context.bot."""
# Create the EventHandler and pass it your bot's token. # Create the EventHandler and pass it your bot's token.
application = Application.builder().token(TELEGRAM_TOKEN).build() updater = Updater(TELEGRAM_TOKEN)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# on different commands - answer in Telegram # on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help)) dp.add_handler(CommandHandler("help", help))
application.add_handler(CommandHandler("license", license)) dp.add_handler(CommandHandler("license", license))
application.add_handler(CommandHandler("trending", trending)) dp.add_handler(CommandHandler("trending", trending))
application.add_handler(CommandHandler("random", rand_pick)) dp.add_handler(CommandHandler("random", rand_pick))
application.add_handler(CommandHandler("donate", donate)) dp.add_handler(CommandHandler("donate", donate))
application.add_handler(CommandHandler("status", status)) dp.add_handler(CommandHandler("status", status))
application.add_handler(CommandHandler("inline", inline_query)) dp.add_handler(CommandHandler("inline", inline_query))
# Charting can be slow so they run async. # Charting can be slow so they run async.
application.add_handler(CommandHandler("intra", intra, block=False)) dp.add_handler(CommandHandler("intra", intra, run_async=True))
application.add_handler(CommandHandler("intraday", intra, block=False)) dp.add_handler(CommandHandler("intraday", intra, run_async=True))
application.add_handler(CommandHandler("day", intra, block=False)) dp.add_handler(CommandHandler("day", intra, run_async=True))
application.add_handler(CommandHandler("chart", chart, block=False)) dp.add_handler(CommandHandler("chart", chart, run_async=True))
application.add_handler(CommandHandler("month", chart, block=False)) dp.add_handler(CommandHandler("month", chart, run_async=True))
# on noncommand i.e message - echo the message on Telegram # on noncommand i.e message - echo the message on Telegram
application.add_handler(MessageHandler(filters.TEXT, symbol_detect)) dp.add_handler(MessageHandler(Filters.text, symbol_detect))
application.add_handler(MessageHandler(filters.PHOTO, symbol_detect_image)) dp.add_handler(MessageHandler(Filters.photo, symbol_detect_image))
# Inline Bot commands # Inline Bot commands
application.add_handler(InlineQueryHandler(inline_query)) dp.add_handler(InlineQueryHandler(inline_query))
# Pre-checkout handler to final check # Pre-checkout handler to final check
application.add_handler(PreCheckoutQueryHandler(precheckout_callback)) dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
# Payment success # Payment success
application.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment_callback)) dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
# log all errors # log all errors
application.add_error_handler(error) dp.add_error_handler(error)
# Start the Bot # Start the Bot
application.run_polling(allowed_updates=Update.ALL_TYPES) updater.start_polling()
updater.idle()
if __name__ == "__main__": if __name__ == "__main__":
+7 -7
View File
@@ -1,7 +1,7 @@
python-telegram-bot==20.5 python-telegram-bot==13.5
requests==2.31.0 requests==2.25.1
pandas==2.1.0 pandas==2.0.0
schedule==1.2.0 schedule==1.0.0
mplfinance==0.12.10b0 mplfinance==0.12.7a5
markdownify==0.11.6 markdownify==0.6.5
cachetools==5.3.1 cachetools==4.2.2