diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 68d321e..e102104 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,20 +1,3 @@ -FROM python:3.11-buster AS builder - - -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" ] +FROM python:3.11 + +COPY . . diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index de09b46..4f26f5e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,26 +1,39 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.0/containers/docker-existing-dockerfile +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "DockerDev", - // Sets the run context to one level up instead of the .devcontainer folder. - "context": "..", - // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. - "dockerFile": "Dockerfile", - // Set *default* container specific settings.json values on container create. - "settings": {}, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + // "image": "mcr.microsoft.com/devcontainers/python:1-3-bookworm", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers-contrib/features/black:2": {}, + "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-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": "apt-get update && apt-get install -y curl", - // Uncomment when using a ptrace-based debugger like C++, Go, and Rust - // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], - // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. - // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], - // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. - // "remoteUser": "vscode" + } + }, + "postCreateCommand": "pip3 install --user -r dev-reqs.txt" + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 35f8a4e..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "configurations": [ - { - "name": "Telegram Bot", - "type": "python", - "request": "launch", - "program": "bot.py", - "console": "integratedTerminal", - "python": "python3.11" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e2640e2..00771b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { - "python.formatting.provider": "black", - "python.linting.mypyEnabled": true, - "python.linting.flake8Enabled": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.formatOnSaveMode": "modificationsIfAvailable", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + } } \ No newline at end of file diff --git a/common/MarketData.py b/common/MarketData.py index 3bf1a9c..cdde21e 100644 --- a/common/MarketData.py +++ b/common/MarketData.py @@ -103,7 +103,7 @@ class MarketData: ) status.raise_for_status() 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: 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": 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: 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}/"): 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 " @@ -148,11 +152,13 @@ class MarketData: def spark_reply(self, symbol: Stock) -> str: if quoteResp := self.get(f"stocks/quotes/{symbol}/"): - changePercent = round(quoteResp["changepct"][0], 2) - return f"`{symbol.tag}`: {changePercent}%" - else: - logging.warning(f"{symbol} did not have 'changepct' field.") - return f"`{symbol.tag}`" + try: + changePercent = round(quoteResp["changepct"][0], 2) + return f"`{symbol.tag}`: {changePercent}%" + except TypeError: + pass + + return f"`{symbol.tag}`" 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. diff --git a/common/cg_Crypto.py b/common/cg_Crypto.py index aa2f3af..dad38d0 100644 --- a/common/cg_Crypto.py +++ b/common/cg_Crypto.py @@ -77,10 +77,10 @@ class cg_Crypto: try: status.raise_for_status() 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: - 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: """Returns current market price or after hours if its available for a given coin symbol. diff --git a/common/symbol_router.py b/common/symbol_router.py index f85b142..c0ec4ca 100644 --- a/common/symbol_router.py +++ b/common/symbol_router.py @@ -123,7 +123,8 @@ class Router: 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( 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" if coins: - reply += "\n\nšŸ¦ŽTrending Crypto:\n`" - reply += "━" * len("Trending Crypto:") + "`\n" + reply += "\n\nšŸ¦ŽTrending on CoinGecko:\n`" + reply += "━" * len("Trending on CoinGecko:") + "`\n" for coin in coins: reply += coin + "\n" @@ -353,7 +354,8 @@ class Router: return "Trending data is not currently available." 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") return f"{choice}\nBuy and hold until: {hold}" diff --git a/dev-reqs.txt b/dev-reqs.txt index 39c1246..a0a4367 100644 --- a/dev-reqs.txt +++ b/dev-reqs.txt @@ -1,8 +1,9 @@ -r telegram/requirements.txt -black==23.3.0 -flake8==5.0.4 +black==23.7.0 +flake8==6.1.0 Flake8-pyproject==1.2.3 pylama==8.4.1 -mypy==1.2.0 -types-cachetools==5.3.0.5 -types-pytz==2023.3.0.0 \ No newline at end of file +mypy==1.5.1 +types-cachetools==5.3.0.6 +types-pytz==2023.3.0.1 +ruff==0.0.287 \ No newline at end of file diff --git a/discord/bot.py b/discord/bot.py index 8da2929..300f6a0 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -42,7 +42,7 @@ async def status(ctx: commands): message = "" try: 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: logging.critical(ex) diff --git a/pyproject.toml b/pyproject.toml index e87df56..225979c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,4 +5,7 @@ line-length = 130 max-line-length = 130 [tool.pycodestyle] -max_line_length = 130 \ No newline at end of file +max_line_length = 130 + +[tool.ruff] +line-length = 130 \ No newline at end of file diff --git a/telegram/bot.py b/telegram/bot.py index f54966d..fd7fd6c 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -18,14 +18,15 @@ from telegram import ( LabeledPrice, Update, ) + from telegram.ext import ( - CallbackContext, + Application, CommandHandler, - Filters, InlineQueryHandler, - MessageHandler, PreCheckoutQueryHandler, - Updater, + MessageHandler, + filters, + ContextTypes, ) from common.symbol_router import Router @@ -33,6 +34,10 @@ from T_info import T_info # Enable logging 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__) TELEGRAM_TOKEN = os.environ["TELEGRAM"] @@ -50,58 +55,58 @@ t = T_info() 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.""" log.info(f"Start command ran by {update.message.chat.username}") - update.message.reply_text( + await update.message.reply_text( text=t.help_text, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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.""" log.info(f"Help command ran by {update.message.chat.username}") - update.message.reply_text( + await update.message.reply_text( text=t.help_text, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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.""" log.info(f"License command ran by {update.message.chat.username}") - update.message.reply_text( + await update.message.reply_text( text=t.license, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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.""" 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_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, - 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.""" log.info(f"Donate command ran by {update.message.chat.username}") chat_id = update.message.chat_id 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, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, disable_notification=True, ) amount = 1.0 @@ -111,11 +116,11 @@ def donate(update: Update, context: CallbackContext): try: price = int(amount * 100) 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 log.info(f"Donation amount: {price} by {update.message.chat.username}") - context.bot.send_invoice( + await context.bot.send_invoice( chat_id=chat_id, title="Simple Stock Bot Donation", 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""" log.info("precheckout_callback queried") 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. # if query.invoice_payload == "simple-stock-bot": # # answer False pre_checkout_query - # query.answer(ok=True) + # await query.answer(ok=True) # 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""" 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` 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: if update.message.caption: update.message.text = update.message.caption - symbol_detect(update, context) + await symbol_detect(update, context) except AttributeError: 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, then returns the prices of any symbols found. @@ -182,18 +187,18 @@ def symbol_detect(update: Update, context: CallbackContext): return if symbols: # 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}") for reply in s.price_reply(symbols): - update.message.reply_text( + await update.message.reply_text( text=reply, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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""" 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 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" ) return @@ -212,19 +217,19 @@ def intra(update: Update, context: CallbackContext): if len(symbols): symbol = symbols[0] else: - update.message.reply_text("No symbols or coins found.") + await update.message.reply_text("No symbols or coins found.") return df = s.intra_reply(symbol) if df.empty: - update.message.reply_text( + await update.message.reply_text( text="Invalid symbol please see `/help` for usage details.", - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, disable_notification=True, ) 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() mpf.plot( @@ -237,17 +242,17 @@ def intra(update: Update, context: CallbackContext): ) buf.seek(0) - update.message.reply_photo( + await update.message.reply_photo( photo=buf, 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"\n\n{s.price_reply([symbol])[0]}", - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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""" 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 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" ) return @@ -265,18 +270,18 @@ def chart(update: Update, context: CallbackContext): if len(symbols): symbol = symbols[0] else: - update.message.reply_text("No symbols or coins found.") + await update.message.reply_text("No symbols or coins found.") return df = s.chart_reply(symbol) if df.empty: - update.message.reply_text( + await update.message.reply_text( text="Invalid symbol please see `/help` for usage details.", - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, disable_notification=True, ) 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() mpf.plot( @@ -289,39 +294,42 @@ def chart(update: Update, context: CallbackContext): ) buf.seek(0) - update.message.reply_photo( + await update.message.reply_photo( photo=buf, 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]}", - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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.""" log.info(f"Trending command ran by {update.message.chat.username}") 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() - update.message.reply_text( + await update.message.reply_text( text=trending_list, - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, 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 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 = {"$", "$$", " ", ""} @@ -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. """ - update.inline_query.answer( + await update.inline_query.answer( [ InlineQueryResultArticle( str(uuid4()), 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( str(uuid4()), 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: - 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") 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""" 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(), - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, disable_notification=True, ) -def error(update: Update, context: CallbackContext): +async def error(update: Update, context: ContextTypes.DEFAULT_TYPE): """Log Errors caused by Updates.""" 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)}" ) - 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}`", - parse_mode=telegram.ParseMode.MARKDOWN, + parse_mode=telegram.constants.ParseMode.MARKDOWN, ) else: log.warning("No message to send to user.") @@ -402,48 +414,43 @@ def error(update: Update, context: CallbackContext): def main(): """Start the context.bot.""" # Create the EventHandler and pass it your bot's token. - updater = Updater(TELEGRAM_TOKEN) - - # Get the dispatcher to register handlers - dp = updater.dispatcher + application = Application.builder().token(TELEGRAM_TOKEN).build() # on different commands - answer in Telegram - dp.add_handler(CommandHandler("start", start)) - dp.add_handler(CommandHandler("help", help)) - dp.add_handler(CommandHandler("license", license)) - dp.add_handler(CommandHandler("trending", trending)) - dp.add_handler(CommandHandler("random", rand_pick)) - dp.add_handler(CommandHandler("donate", donate)) - dp.add_handler(CommandHandler("status", status)) - dp.add_handler(CommandHandler("inline", inline_query)) + application.add_handler(CommandHandler("start", start)) + application.add_handler(CommandHandler("help", help)) + application.add_handler(CommandHandler("license", license)) + application.add_handler(CommandHandler("trending", trending)) + application.add_handler(CommandHandler("random", rand_pick)) + application.add_handler(CommandHandler("donate", donate)) + application.add_handler(CommandHandler("status", status)) + application.add_handler(CommandHandler("inline", inline_query)) # Charting can be slow so they run async. - dp.add_handler(CommandHandler("intra", intra, run_async=True)) - dp.add_handler(CommandHandler("intraday", intra, run_async=True)) - dp.add_handler(CommandHandler("day", intra, run_async=True)) - dp.add_handler(CommandHandler("chart", chart, run_async=True)) - dp.add_handler(CommandHandler("month", chart, run_async=True)) + application.add_handler(CommandHandler("intra", intra, block=False)) + application.add_handler(CommandHandler("intraday", intra, block=False)) + application.add_handler(CommandHandler("day", intra, block=False)) + application.add_handler(CommandHandler("chart", chart, block=False)) + application.add_handler(CommandHandler("month", chart, block=False)) # on noncommand i.e message - echo the message on Telegram - dp.add_handler(MessageHandler(Filters.text, symbol_detect)) - dp.add_handler(MessageHandler(Filters.photo, symbol_detect_image)) + application.add_handler(MessageHandler(filters.TEXT, symbol_detect)) + application.add_handler(MessageHandler(filters.PHOTO, symbol_detect_image)) # Inline Bot commands - dp.add_handler(InlineQueryHandler(inline_query)) + application.add_handler(InlineQueryHandler(inline_query)) # Pre-checkout handler to final check - dp.add_handler(PreCheckoutQueryHandler(precheckout_callback)) + application.add_handler(PreCheckoutQueryHandler(precheckout_callback)) # 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 - dp.add_error_handler(error) + application.add_error_handler(error) # Start the Bot - updater.start_polling() - - updater.idle() + application.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == "__main__": diff --git a/telegram/requirements.txt b/telegram/requirements.txt index 4057ec0..344dfd4 100644 --- a/telegram/requirements.txt +++ b/telegram/requirements.txt @@ -1,7 +1,7 @@ -python-telegram-bot==13.5 -requests==2.25.1 -pandas==2.0.0 -schedule==1.0.0 -mplfinance==0.12.7a5 -markdownify==0.6.5 -cachetools==4.2.2 \ No newline at end of file +python-telegram-bot==20.5 +requests==2.31.0 +pandas==2.1.0 +schedule==1.2.0 +mplfinance==0.12.10b0 +markdownify==0.11.6 +cachetools==5.3.1 \ No newline at end of file