fix logging messages and add TG listener

master
Ernest Litvinenko 2024-03-05 14:46:43 +03:00
parent 36532fc1c9
commit 9745d580d4
8 changed files with 139 additions and 10 deletions

Binary file not shown.

View File

@ -109,7 +109,7 @@ class ExcelParser:
return {"price": price, return {"price": price,
"vat": int(data['percent_vat']), "vat": int(data['percent_vat']),
"max_days": int(data['maxdays']), "max_days": int(data['maxdays']),
"transport_delivery_date": df["Дата загрузки"]} "transport_delivery_date": str(df["Дата загрузки"][0])}
self.add_link_to_database(query, answer=data) self.add_link_to_database(query, answer=data)
return None return None

86
main.py
View File

@ -18,6 +18,14 @@ from storage import Storage
from telegram_logs import logger from telegram_logs import logger
import dotenv import dotenv
import asyncio
import threading
from webserver import dp
from aiogram.filters import CommandStart
from aiogram.types import Message, ReplyKeyboardMarkup, KeyboardButton
from webserver import bot
dotenv.load_dotenv('.env') dotenv.load_dotenv('.env')
IS_PROD = os.environ.get('PROD_ENV') == '1' IS_PROD = os.environ.get('PROD_ENV') == '1'
@ -51,6 +59,7 @@ class Parser:
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val is not None:
logger.error("Бот остановлен. Причина: " + str(exc_val)) logger.error("Бот остановлен. Причина: " + str(exc_val))
print("Gracefully shutting down...") print("Gracefully shutting down...")
@ -100,13 +109,18 @@ class Parser:
0] not in self.storage.get_links()] 0] not in self.storage.get_links()]
for link in links: for link in links:
if PARSER_ALIVE is False:
raise KeyboardInterrupt("Бот остановлен по запросу")
logger.info("Обработка заявки: " + link) logger.info("Обработка заявки: " + link)
try: try:
self.accept_documentation(link) self.accept_documentation(link)
except Exception as exc: except Exception as exc:
logger.error("Не удалось обработать заявку. Подробности: " + str(exc)) logger.error("Не удалось обработать заявку. Подробности: " + str(exc))
logger.info("Все LTL заявки обработаны, обновление через 60сек")
def parse(self, url: str = None) -> dict: def parse(self, url: str = None) -> dict:
if PARSER_ALIVE is False:
raise KeyboardInterrupt("Бот остановлен по запросу")
fp = self.download_documentation() fp = self.download_documentation()
e_parser = ExcelParser(fp, url) e_parser = ExcelParser(fp, url)
price = e_parser.calculate() price = e_parser.calculate()
@ -116,6 +130,9 @@ class Parser:
return price return price
def accept_documentation(self, url: str): def accept_documentation(self, url: str):
if PARSER_ALIVE is False:
raise KeyboardInterrupt("Бот остановлен по запросу")
time.sleep(3) time.sleep(3)
self._driver.get(url) self._driver.get(url)
@ -142,6 +159,8 @@ class Parser:
delivery_range=price['max_days']) delivery_range=price['max_days'])
def download_documentation(self) -> list[pathlib.Path]: def download_documentation(self) -> list[pathlib.Path]:
if PARSER_ALIVE is False:
raise KeyboardInterrupt("Бот остановлен по запросу")
try: try:
all_files_1 = set( all_files_1 = set(
pathlib.Path('./downloads') / pathlib.Path(file) for tree in os.walk('./downloads') for file in tree[2]) pathlib.Path('./downloads') / pathlib.Path(file) for tree in os.walk('./downloads') for file in tree[2])
@ -168,6 +187,8 @@ class Parser:
raise KeyboardInterrupt() raise KeyboardInterrupt()
def send_offer_link(self, price: int, nds: int, delivery_range: str, delivery_time: str): def send_offer_link(self, price: int, nds: int, delivery_range: str, delivery_time: str):
if PARSER_ALIVE is False:
raise KeyboardInterrupt("Бот остановлен по запросу")
try: try:
logger.info( logger.info(
f"Предварительные данные по заявке: Цена: {price}, НДС: {nds}%, Доставка: {delivery_range} дн., Подача машины {delivery_time}") f"Предварительные данные по заявке: Цена: {price}, НДС: {nds}%, Доставка: {delivery_range} дн., Подача машины {delivery_time}")
@ -220,10 +241,71 @@ class Parser:
time.sleep(10) time.sleep(10)
if __name__ == "__main__": PARSER_ALIVE = True
def parse_runner():
with Parser() as parser: with Parser() as parser:
parser.login() parser.login()
while True: while True:
parser.search() parser.search()
logger.info("Все LTL заявки обработаны, обновление через 60сек")
time.sleep(60) time.sleep(60)
parser_thread = threading.Thread(target=parse_runner, daemon=True)
@dp.message(CommandStart())
async def start_handler(message: Message):
s = Storage()
if message.from_user.id not in s.get_users():
await message.answer("Вы не зарегистрированы, обратитесь к администратору")
return
markup = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="Запустить Бот")]])
await message.answer(f"Hello, {message.from_user.full_name}!", reply_markup=markup)
@dp.message()
async def message_handler(message: Message):
global PARSER_ALIVE
s = Storage()
if message.from_user.id not in s.get_users():
await message.answer("Вы не зарегистрированы, обратитесь к администратору")
return
markup = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="Запустить Бот")]])
if message.text == "Запустить Бот":
PARSER_ALIVE = True
for chat_id in s.get_users():
await bot.send_message(chat_id,
f"Пользователь {message.from_user.full_name} запускает бот",
reply_markup=markup)
parser_thread.start()
return
if message.text == "Остановить Бот":
for chat_id in s.get_users():
await bot.send_message(chat_id,
f"Пользователь {message.from_user.full_name} остановил бот",
reply_markup=markup)
PARSER_ALIVE = False
await message.answer("Бот остановлен", reply_markup=markup)
return
await message.answer("Неизвестная команда")
async def main():
storage = Storage()
markup = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="Запустить Бот")]])
for chat_id in storage.get_users():
await bot.send_message(chat_id,
"Контроллер запущен, бот ожидает включения",
reply_markup=markup,
disable_notification=True)
await dp.start_polling(bot)
if __name__ == "__main__":
# with Parser() as p:
# p.login()
# p.search()
asyncio.run(main())

View File

@ -12,3 +12,4 @@ pyexcel-xls
pyexcel-xlsx pyexcel-xlsx
loguru loguru
pydotenv pydotenv
aiogram

View File

@ -4,14 +4,28 @@
# #
# pip-compile # pip-compile
# #
aiofiles==23.2.1
# via aiogram
aiogram==3.4.1
# via -r requirements.in
aiohttp==3.9.3
# via aiogram
aiosignal==1.3.1
# via aiohttp
annotated-types==0.6.0
# via pydantic
async-timeout==4.0.3
# via aiohttp
attrs==23.2.0 attrs==23.2.0
# via # via
# aiohttp
# outcome # outcome
# trio # trio
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
# via xls2xlsx # via xls2xlsx
certifi==2023.11.17 certifi==2023.11.17
# via # via
# aiogram
# requests # requests
# selenium # selenium
chardet==5.2.0 chardet==5.2.0
@ -32,18 +46,29 @@ exceptiongroup==1.2.0
# trio-websocket # trio-websocket
fonttools==4.47.2 fonttools==4.47.2
# via xls2xlsx # via xls2xlsx
frozenlist==1.4.1
# via
# aiohttp
# aiosignal
h11==0.14.0 h11==0.14.0
# via wsproto # via wsproto
idna==3.6 idna==3.6
# via # via
# requests # requests
# trio # trio
# yarl
lml==0.1.0 lml==0.1.0
# via # via
# pyexcel # pyexcel
# pyexcel-io # pyexcel-io
loguru==0.7.2 loguru==0.7.2
# via -r requirements.in # via -r requirements.in
magic-filter==1.0.12
# via aiogram
multidict==6.0.5
# via
# aiohttp
# yarl
numpy==1.26.3 numpy==1.26.3
# via pandas # via pandas
openpyxl==3.1.2 openpyxl==3.1.2
@ -59,6 +84,10 @@ pandas==2.2.0
# via -r requirements.in # via -r requirements.in
pillow==10.2.0 pillow==10.2.0
# via xls2xlsx # via xls2xlsx
pydantic==2.5.3
# via aiogram
pydantic-core==2.14.6
# via pydantic
pydotenv==0.0.7 pydotenv==0.0.7
# via -r requirements.in # via -r requirements.in
pyexcel==0.7.0 pyexcel==0.7.0
@ -108,7 +137,11 @@ trio==0.24.0
trio-websocket==0.11.1 trio-websocket==0.11.1
# via selenium # via selenium
typing-extensions==4.9.0 typing-extensions==4.9.0
# via selenium # via
# aiogram
# pydantic
# pydantic-core
# selenium
tzdata==2023.4 tzdata==2023.4
# via pandas # via pandas
urllib3[socks]==2.1.0 urllib3[socks]==2.1.0
@ -137,3 +170,5 @@ xlwt==1.3.0
# -r requirements.in # -r requirements.in
# pyexcel-xls # pyexcel-xls
# xlutils # xlutils
yarl==1.9.4
# via aiohttp

View File

@ -5,10 +5,15 @@ from typing import ContextManager
class Storage: class Storage:
def __init__(self): def __init__(self):
self.con = None
def set_connection(self):
self.con = sqlite3.connect("database.db") self.con = sqlite3.connect("database.db")
@contextmanager @contextmanager
def get_cursor(self) -> ContextManager[sqlite3.Cursor]: def get_cursor(self) -> ContextManager[sqlite3.Cursor]:
if self.con is None:
self.set_connection()
cur = self.con.cursor() cur = self.con.cursor()
try: try:
yield cur yield cur

View File

@ -1,3 +1,5 @@
import json
import requests import requests
from loguru import logger from loguru import logger
from storage import Storage from storage import Storage
@ -19,11 +21,11 @@ def _log(message):
r = requests.post("{0}{1}/sendMessage".format(_URL, _TOKEN), { r = requests.post("{0}{1}/sendMessage".format(_URL, _TOKEN), {
"chat_id": int(chat_id), "chat_id": int(chat_id),
"disable_notification": True, "disable_notification": True,
"text": icon + message "text": icon + message,
"reply_markup": json.dumps({"keyboard": [[{"text": "Остановить Бот"}]]})
}) })
if r.status_code != 200:
if r.status_code >= 400: print(r.json())
logger.error("Failed to send message: {0} {1}".format(r.status_code, r.text))
def _filter_info_only(record): def _filter_info_only(record):

4
webserver.py Normal file
View File

@ -0,0 +1,4 @@
from aiogram import Bot, Dispatcher
dp = Dispatcher()
bot = Bot(token="6767909836:AAFpsqtWeBNIBgSSi2_19rltEHOF0mrvTg0")