mirror of
https://gitlab.com/simple-stock-bots/simple-stock-bot.git
synced 2025-06-15 23:06:40 +00:00
Merge branch '99-inline-functionality-is-broken' into 'master'
Resolve "inline functionality is broken" Closes #99 See merge request simple-stock-bots/simple-telegram-stock-bot!48
This commit is contained in:
commit
ad4262777d
@ -1,20 +1,3 @@
|
|||||||
FROM python:3.11-buster AS builder
|
FROM python:3.11
|
||||||
|
|
||||||
|
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" ]
|
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.0/containers/docker-existing-dockerfile
|
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
||||||
{
|
{
|
||||||
"name": "DockerDev",
|
"name": "Python 3",
|
||||||
// Sets the run context to one level up instead of the .devcontainer folder.
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
"context": "..",
|
// "image": "mcr.microsoft.com/devcontainers/python:1-3-bookworm",
|
||||||
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
"build": {
|
||||||
"dockerFile": "Dockerfile",
|
"dockerfile": "Dockerfile"
|
||||||
// Set *default* container specific settings.json values on container create.
|
},
|
||||||
"settings": {},
|
"features": {
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
"ghcr.io/devcontainers-contrib/features/black:2": {},
|
||||||
"extensions": [
|
"ghcr.io/devcontainers-contrib/features/mypy:2": {},
|
||||||
|
"ghcr.io/devcontainers-contrib/features/pylint:2": {},
|
||||||
|
"ghcr.io/devcontainers/features/docker-in-docker": {}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-azuretools.vscode-docker"
|
"ms-python.black-formatter",
|
||||||
|
"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": [],
|
},
|
||||||
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
"postCreateCommand": "pip3 install --user -r dev-reqs.txt"
|
||||||
// "postCreateCommand": "apt-get update && apt-get install -y curl",
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
|
// "features": {},
|
||||||
// "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
|
// "forwardPorts": [],
|
||||||
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
// "postCreateCommand": "pip3 install --user -r requirements.txt",
|
||||||
// "remoteUser": "vscode"
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
}
|
}
|
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Telegram Bot",
|
|
||||||
"type": "python",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "bot.py",
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"python": "python3.11"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"python.formatting.provider": "black",
|
"editor.formatOnSave": true,
|
||||||
"python.linting.mypyEnabled": true,
|
"editor.formatOnPaste": true,
|
||||||
"python.linting.flake8Enabled": true,
|
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
|
}
|
||||||
}
|
}
|
@ -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,7 +131,11 @@ 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 "
|
||||||
|
|
||||||
@ -148,11 +152,13 @@ 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}/"):
|
||||||
changePercent = round(quoteResp["changepct"][0], 2)
|
try:
|
||||||
return f"`{symbol.tag}`: {changePercent}%"
|
changePercent = round(quoteResp["changepct"][0], 2)
|
||||||
else:
|
return f"`{symbol.tag}`: {changePercent}%"
|
||||||
logging.warning(f"{symbol} did not have 'changepct' field.")
|
except TypeError:
|
||||||
return f"`{symbol.tag}`"
|
pass
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -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.
|
||||||
|
@ -123,7 +123,8 @@ 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()
|
||||||
@ -338,8 +339,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 Crypto:\n`"
|
reply += "\n\n🦎Trending on CoinGecko:\n`"
|
||||||
reply += "━" * len("Trending Crypto:") + "`\n"
|
reply += "━" * len("Trending on CoinGecko:") + "`\n"
|
||||||
for coin in coins:
|
for coin in coins:
|
||||||
reply += coin + "\n"
|
reply += coin + "\n"
|
||||||
|
|
||||||
@ -353,7 +354,8 @@ 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}"
|
||||||
|
11
dev-reqs.txt
11
dev-reqs.txt
@ -1,8 +1,9 @@
|
|||||||
-r telegram/requirements.txt
|
-r telegram/requirements.txt
|
||||||
black==23.3.0
|
black==23.7.0
|
||||||
flake8==5.0.4
|
flake8==6.1.0
|
||||||
Flake8-pyproject==1.2.3
|
Flake8-pyproject==1.2.3
|
||||||
pylama==8.4.1
|
pylama==8.4.1
|
||||||
mypy==1.2.0
|
mypy==1.5.1
|
||||||
types-cachetools==5.3.0.5
|
types-cachetools==5.3.0.6
|
||||||
types-pytz==2023.3.0.0
|
types-pytz==2023.3.0.1
|
||||||
|
ruff==0.0.287
|
@ -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*1000:.4f}ms") + "\n"
|
message += s.status(f"Bot recieved your message in: {bot.latency*10:.4f} seconds") + "\n"
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logging.critical(ex)
|
logging.critical(ex)
|
||||||
|
@ -5,4 +5,7 @@ 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
|
193
telegram/bot.py
193
telegram/bot.py
@ -18,14 +18,15 @@ from telegram import (
|
|||||||
LabeledPrice,
|
LabeledPrice,
|
||||||
Update,
|
Update,
|
||||||
)
|
)
|
||||||
|
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
CallbackContext,
|
Application,
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
Filters,
|
|
||||||
InlineQueryHandler,
|
InlineQueryHandler,
|
||||||
MessageHandler,
|
|
||||||
PreCheckoutQueryHandler,
|
PreCheckoutQueryHandler,
|
||||||
Updater,
|
MessageHandler,
|
||||||
|
filters,
|
||||||
|
ContextTypes,
|
||||||
)
|
)
|
||||||
|
|
||||||
from common.symbol_router import Router
|
from common.symbol_router import Router
|
||||||
@ -33,6 +34,10 @@ 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"]
|
||||||
@ -50,58 +55,58 @@ t = T_info()
|
|||||||
log.info("Bot script started.")
|
log.info("Bot script started.")
|
||||||
|
|
||||||
|
|
||||||
def start(update: Update, context: CallbackContext):
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=t.help_text,
|
text=t.help_text,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def help(update: Update, context: CallbackContext):
|
async def help(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=t.help_text,
|
text=t.help_text,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def license(update: Update, context: CallbackContext):
|
async def license(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=t.license,
|
text=t.license,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def status(update: Update, context: CallbackContext):
|
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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.")
|
||||||
|
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=bot_status,
|
text=bot_status,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def donate(update: Update, context: CallbackContext):
|
async def donate(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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:
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=t.donate_text,
|
text=t.donate_text,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
amount = 1.0
|
amount = 1.0
|
||||||
@ -111,11 +116,11 @@ def donate(update: Update, context: CallbackContext):
|
|||||||
try:
|
try:
|
||||||
price = int(amount * 100)
|
price = int(amount * 100)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
update.message.reply_text(f"{amount} is not a valid donation amount or number.")
|
await 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}")
|
||||||
|
|
||||||
context.bot.send_invoice(
|
await 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}",
|
||||||
@ -131,27 +136,27 @@ def donate(update: Update, context: CallbackContext):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def precheckout_callback(update: Update, context: CallbackContext):
|
async def precheckout_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Approves donation"""
|
"""Approves donation"""
|
||||||
log.info("precheckout_callback queried")
|
log.info("precheckout_callback queried")
|
||||||
query = update.pre_checkout_query
|
query = update.pre_checkout_query
|
||||||
|
|
||||||
query.answer(ok=True)
|
await 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
|
||||||
# query.answer(ok=True)
|
# await query.answer(ok=True)
|
||||||
# else:
|
# else:
|
||||||
# query.answer(ok=False, error_message="Something went wrong...")
|
# await query.answer(ok=False, error_message="Something went wrong...")
|
||||||
|
|
||||||
|
|
||||||
def successful_payment_callback(update: Update, context: CallbackContext):
|
async def successful_payment_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Thanks user for donation"""
|
"""Thanks user for donation"""
|
||||||
log.info("Successful payment!")
|
log.info("Successful payment!")
|
||||||
update.message.reply_text("Thank you for your donation! It goes a long way to keeping the bot free!")
|
await update.message.reply_text("Thank you for your donation! It goes a long way to keeping the bot free!")
|
||||||
|
|
||||||
|
|
||||||
def symbol_detect_image(update: Update, context: CallbackContext):
|
async def symbol_detect_image(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -159,12 +164,12 @@ def symbol_detect_image(update: Update, context: CallbackContext):
|
|||||||
try:
|
try:
|
||||||
if update.message.caption:
|
if update.message.caption:
|
||||||
update.message.text = update.message.caption
|
update.message.text = update.message.caption
|
||||||
symbol_detect(update, context)
|
await symbol_detect(update, context)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def symbol_detect(update: Update, context: CallbackContext):
|
async def symbol_detect(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
@ -182,18 +187,18 @@ def symbol_detect(update: Update, context: CallbackContext):
|
|||||||
return
|
return
|
||||||
if symbols:
|
if symbols:
|
||||||
# Let user know bot is working
|
# Let user know bot is working
|
||||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.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):
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=reply,
|
text=reply,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def intra(update: Update, context: CallbackContext):
|
async def intra(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
|
|
||||||
@ -201,7 +206,7 @@ def intra(update: Update, context: CallbackContext):
|
|||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
if message.strip().split("@")[0] == "/intra":
|
if message.strip().split("@")[0] == "/intra":
|
||||||
update.message.reply_text(
|
await 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
|
||||||
@ -212,19 +217,19 @@ def intra(update: Update, context: CallbackContext):
|
|||||||
if len(symbols):
|
if len(symbols):
|
||||||
symbol = symbols[0]
|
symbol = symbols[0]
|
||||||
else:
|
else:
|
||||||
update.message.reply_text("No symbols or coins found.")
|
await 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:
|
||||||
update.message.reply_text(
|
await 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.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO)
|
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.UPLOAD_PHOTO)
|
||||||
|
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
mpf.plot(
|
mpf.plot(
|
||||||
@ -237,17 +242,17 @@ def intra(update: Update, context: CallbackContext):
|
|||||||
)
|
)
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
update.message.reply_photo(
|
await 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.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def chart(update: Update, context: CallbackContext):
|
async def chart(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
|
|
||||||
@ -255,7 +260,7 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
if message.strip().split("@")[0] == "/chart":
|
if message.strip().split("@")[0] == "/chart":
|
||||||
update.message.reply_text(
|
await 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
|
||||||
@ -265,18 +270,18 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
if len(symbols):
|
if len(symbols):
|
||||||
symbol = symbols[0]
|
symbol = symbols[0]
|
||||||
else:
|
else:
|
||||||
update.message.reply_text("No symbols or coins found.")
|
await 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:
|
||||||
update.message.reply_text(
|
await 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.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO)
|
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.UPLOAD_PHOTO)
|
||||||
|
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
mpf.plot(
|
mpf.plot(
|
||||||
@ -289,39 +294,42 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
)
|
)
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
update.message.reply_photo(
|
await 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.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def trending(update: Update, context: CallbackContext):
|
async def trending(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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
|
||||||
|
|
||||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
await context.bot.send_chat_action(chat_id=chat_id, action=telegram.constants.ChatAction.TYPING)
|
||||||
|
|
||||||
trending_list = s.trending()
|
trending_list = s.trending()
|
||||||
|
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=trending_list,
|
text=trending_list,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def inline_query(update: Update, context: CallbackContext):
|
async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
"""
|
"""
|
||||||
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}")
|
|
||||||
log.info(f"Query: {update.inline_query.query}")
|
if not update.inline_query.query:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Query: {update.inline_query.query}")
|
||||||
|
|
||||||
ignored_queries = {"$", "$$", " ", ""}
|
ignored_queries = {"$", "$$", " ", ""}
|
||||||
|
|
||||||
@ -331,12 +339,14 @@ def inline_query(update: Update, context: CallbackContext):
|
|||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
update.inline_query.answer(
|
await 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(default_message, parse_mode=telegram.ParseMode.MARKDOWN),
|
input_message_content=InputTextMessageContent(
|
||||||
|
default_message, parse_mode=telegram.constants.ParseMode.MARKDOWN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -349,29 +359,31 @@ def inline_query(update: Update, context: CallbackContext):
|
|||||||
InlineQueryResultArticle(
|
InlineQueryResultArticle(
|
||||||
str(uuid4()),
|
str(uuid4()),
|
||||||
title=row["description"],
|
title=row["description"],
|
||||||
input_message_content=InputTextMessageContent(row["price_reply"], parse_mode=telegram.ParseMode.MARKDOWN),
|
input_message_content=InputTextMessageContent(
|
||||||
|
row["price_reply"], parse_mode=telegram.constants.ParseMode.MARKDOWN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(results) == 5:
|
if len(results) == 5:
|
||||||
update.inline_query.answer(results, cache_time=60 * 60)
|
await update.inline_query.answer(results, cache_time=60 * 60)
|
||||||
log.info("Inline Command was successful")
|
log.info("Inline Command was successful")
|
||||||
return
|
return
|
||||||
update.inline_query.answer(results)
|
await update.inline_query.answer(results)
|
||||||
|
|
||||||
|
|
||||||
def rand_pick(update: Update, context: CallbackContext):
|
async def rand_pick(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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}")
|
||||||
|
|
||||||
update.message.reply_text(
|
await update.message.reply_text(
|
||||||
text=s.random_pick(),
|
text=s.random_pick(),
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
disable_notification=True,
|
disable_notification=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def error(update: Update, context: CallbackContext):
|
async def error(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""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)
|
||||||
|
|
||||||
@ -390,9 +402,9 @@ def error(update: Update, context: CallbackContext):
|
|||||||
f"\t{html.escape(tb_string)}"
|
f"\t{html.escape(tb_string)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
update.message.reply_text(
|
await 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.ParseMode.MARKDOWN,
|
parse_mode=telegram.constants.ParseMode.MARKDOWN,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.warning("No message to send to user.")
|
log.warning("No message to send to user.")
|
||||||
@ -402,48 +414,43 @@ def error(update: Update, context: CallbackContext):
|
|||||||
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.
|
||||||
updater = Updater(TELEGRAM_TOKEN)
|
application = Application.builder().token(TELEGRAM_TOKEN).build()
|
||||||
|
|
||||||
# Get the dispatcher to register handlers
|
|
||||||
dp = updater.dispatcher
|
|
||||||
|
|
||||||
# on different commands - answer in Telegram
|
# on different commands - answer in Telegram
|
||||||
dp.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
dp.add_handler(CommandHandler("help", help))
|
application.add_handler(CommandHandler("help", help))
|
||||||
dp.add_handler(CommandHandler("license", license))
|
application.add_handler(CommandHandler("license", license))
|
||||||
dp.add_handler(CommandHandler("trending", trending))
|
application.add_handler(CommandHandler("trending", trending))
|
||||||
dp.add_handler(CommandHandler("random", rand_pick))
|
application.add_handler(CommandHandler("random", rand_pick))
|
||||||
dp.add_handler(CommandHandler("donate", donate))
|
application.add_handler(CommandHandler("donate", donate))
|
||||||
dp.add_handler(CommandHandler("status", status))
|
application.add_handler(CommandHandler("status", status))
|
||||||
dp.add_handler(CommandHandler("inline", inline_query))
|
application.add_handler(CommandHandler("inline", inline_query))
|
||||||
|
|
||||||
# Charting can be slow so they run async.
|
# Charting can be slow so they run async.
|
||||||
dp.add_handler(CommandHandler("intra", intra, run_async=True))
|
application.add_handler(CommandHandler("intra", intra, block=False))
|
||||||
dp.add_handler(CommandHandler("intraday", intra, run_async=True))
|
application.add_handler(CommandHandler("intraday", intra, block=False))
|
||||||
dp.add_handler(CommandHandler("day", intra, run_async=True))
|
application.add_handler(CommandHandler("day", intra, block=False))
|
||||||
dp.add_handler(CommandHandler("chart", chart, run_async=True))
|
application.add_handler(CommandHandler("chart", chart, block=False))
|
||||||
dp.add_handler(CommandHandler("month", chart, run_async=True))
|
application.add_handler(CommandHandler("month", chart, block=False))
|
||||||
|
|
||||||
# on noncommand i.e message - echo the message on Telegram
|
# on noncommand i.e message - echo the message on Telegram
|
||||||
dp.add_handler(MessageHandler(Filters.text, symbol_detect))
|
application.add_handler(MessageHandler(filters.TEXT, symbol_detect))
|
||||||
dp.add_handler(MessageHandler(Filters.photo, symbol_detect_image))
|
application.add_handler(MessageHandler(filters.PHOTO, symbol_detect_image))
|
||||||
|
|
||||||
# Inline Bot commands
|
# Inline Bot commands
|
||||||
dp.add_handler(InlineQueryHandler(inline_query))
|
application.add_handler(InlineQueryHandler(inline_query))
|
||||||
|
|
||||||
# Pre-checkout handler to final check
|
# Pre-checkout handler to final check
|
||||||
dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
application.add_handler(PreCheckoutQueryHandler(precheckout_callback))
|
||||||
|
|
||||||
# Payment success
|
# Payment success
|
||||||
dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
|
application.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment_callback))
|
||||||
|
|
||||||
# log all errors
|
# log all errors
|
||||||
dp.add_error_handler(error)
|
application.add_error_handler(error)
|
||||||
|
|
||||||
# Start the Bot
|
# Start the Bot
|
||||||
updater.start_polling()
|
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||||
|
|
||||||
updater.idle()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
python-telegram-bot==13.5
|
python-telegram-bot==20.5
|
||||||
requests==2.25.1
|
requests==2.31.0
|
||||||
pandas==2.0.0
|
pandas==2.1.0
|
||||||
schedule==1.0.0
|
schedule==1.2.0
|
||||||
mplfinance==0.12.7a5
|
mplfinance==0.12.10b0
|
||||||
markdownify==0.6.5
|
markdownify==0.11.6
|
||||||
cachetools==4.2.2
|
cachetools==5.3.1
|
Loading…
x
Reference in New Issue
Block a user