init main
This commit is contained in:
parent
9875b41354
commit
5dced765cb
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
venv
|
||||
*pycache*
|
||||
*.xls*
|
||||
*.log
|
||||
*.db
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
12
dj_bot.conf
Normal file
12
dj_bot.conf
Normal file
@ -0,0 +1,12 @@
|
||||
# supervisorctl
|
||||
[program:dj_bot]
|
||||
directory=/root/djimbo_template/
|
||||
command=python3.9 main.py
|
||||
|
||||
autostart=True
|
||||
autorestart=True
|
||||
|
||||
stderr_logfile=/root/djimbo_template/tgbot/data/sv_log_err.log
|
||||
; stderr_logfile_maxbytes=10MB
|
||||
stdout_logfile=/root/djimbo_template/tgbot/data/sv_log_out.log
|
||||
; stdout_logfile_maxbytes=10MB
|
||||
83
main.py
Normal file
83
main.py
Normal file
@ -0,0 +1,83 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
import colorama
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
|
||||
from tgbot.data.config import BOT_TOKEN, BOT_SCHEDULER, get_admins
|
||||
from tgbot.database.db_helper import create_dbx
|
||||
from tgbot.middlewares import register_all_middlwares
|
||||
from tgbot.routers import register_all_routers
|
||||
from tgbot.services.api_session import AsyncRequestSession
|
||||
from tgbot.utils.misc.bot_commands import set_commands
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc_functions import autobackup_admin, startup_notify, tenders_sched, tenders_sched_ap, tenders_sched_ap_in_tenderplan
|
||||
|
||||
colorama.init()
|
||||
|
||||
|
||||
# Запуск шедулеров
|
||||
async def scheduler_start(bot):
|
||||
# BOT_SCHEDULER.add_job(autobackup_admin, trigger="cron", hour=00, args=(bot,)) # Ежедневный Автобэкап в 00:00
|
||||
BOT_SCHEDULER.add_job(tenders_sched_ap_in_tenderplan, 'cron', hour='21', minute='0', args=(bot,)) #
|
||||
# BOT_SCHEDULER.add_job(tenders_sched, 'cron', hour='18', minute='0', args=(bot,)) #
|
||||
# BOT_SCHEDULER.add_job(tenders_sched_ap, 'cron', hour='6', minute='0', args=(bot,)) #
|
||||
|
||||
# scheduler.add_job(func, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2021-05-30')
|
||||
|
||||
|
||||
# Запуск бота и базовых функций
|
||||
async def main():
|
||||
BOT_SCHEDULER.start() # Запуск Шедулера
|
||||
dp = Dispatcher() # Образ Диспетчера
|
||||
arSession = AsyncRequestSession() # Пул асинхронной сессии запросов
|
||||
bot = Bot( # Образ Бота
|
||||
token=BOT_TOKEN,
|
||||
default=DefaultBotProperties(
|
||||
parse_mode="HTML",
|
||||
)
|
||||
)
|
||||
|
||||
register_all_middlwares(dp) # Регистрация всех мидлварей
|
||||
register_all_routers(dp) # Регистрация всех роутеров
|
||||
|
||||
try:
|
||||
await set_commands(bot) # Установка команд
|
||||
await startup_notify(bot) # Уведомления админам при запуске бота
|
||||
await scheduler_start(bot) # Подключение шедулеров
|
||||
|
||||
bot_logger.warning("BOT WAS STARTED")
|
||||
print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Bot was started - @{(await bot.get_me()).username} ~~~~~")
|
||||
print(colorama.Fore.LIGHTBLUE_EX + "~~~~~ ~~~~~")
|
||||
print(colorama.Fore.RESET)
|
||||
|
||||
if len(get_admins()) == 0: print("***** ENTER ADMIN ID IN settings.ini *****")
|
||||
|
||||
await bot.delete_webhook() # Удаление вебхуков, если они имеются
|
||||
await bot.get_updates(offset=-1) # Сброс пендинг апдейтов
|
||||
|
||||
await dp.start_polling(
|
||||
bot,
|
||||
arSession=arSession,
|
||||
allowed_updates=dp.resolve_used_update_types(),
|
||||
)
|
||||
finally:
|
||||
await arSession.close()
|
||||
await bot.session.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_dbx()
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
bot_logger.warning("Bot was stopped")
|
||||
# finally:
|
||||
# if sys.platform.startswith("win"):
|
||||
# os.system("cls")
|
||||
# else:
|
||||
# os.system("clear")
|
||||
97
requirements.txt
Normal file
97
requirements.txt
Normal file
@ -0,0 +1,97 @@
|
||||
aiofiles==23.2.1
|
||||
aiogram==3.4.1
|
||||
aiohttp==3.9.3
|
||||
aiosignal==1.3.1
|
||||
annotated-types==0.6.0
|
||||
anyio==4.3.0
|
||||
appnope==0.1.4
|
||||
APScheduler==3.10.4
|
||||
asttokens==2.4.1
|
||||
async-timeout==4.0.3
|
||||
attrs==23.2.0
|
||||
beautifulsoup4==4.12.3
|
||||
bs4==0.0.2
|
||||
cachetools==5.3.3
|
||||
certifi==2024.2.2
|
||||
charset-normalizer==3.3.2
|
||||
click==8.1.7
|
||||
colorama==0.4.6
|
||||
colorlog==6.8.2
|
||||
comm==0.2.2
|
||||
contourpy==1.2.1
|
||||
cycler==0.12.1
|
||||
debugpy==1.8.1
|
||||
decorator==5.1.1
|
||||
et-xmlfile==1.1.0
|
||||
exceptiongroup==1.2.0
|
||||
executing==2.0.1
|
||||
fastapi==0.110.1
|
||||
fastjsonschema==2.19.1
|
||||
fonttools==4.51.0
|
||||
frozenlist==1.4.1
|
||||
h11==0.14.0
|
||||
idna==3.7
|
||||
ipykernel==6.29.4
|
||||
ipython==8.23.0
|
||||
jedi==0.19.1
|
||||
Jinja2==3.1.3
|
||||
jsonschema==4.21.1
|
||||
jsonschema-specifications==2023.12.1
|
||||
jupyter_client==8.6.1
|
||||
jupyter_core==5.7.2
|
||||
kiwisolver==1.4.5
|
||||
magic-filter==1.0.12
|
||||
MarkupSafe==2.1.5
|
||||
matplotlib==3.8.4
|
||||
matplotlib-inline==0.1.6
|
||||
multidict==6.0.5
|
||||
nbformat==5.10.4
|
||||
nest-asyncio==1.6.0
|
||||
numpy==1.26.4
|
||||
openpyxl==3.1.2
|
||||
packaging==24.0
|
||||
pandas==2.2.2
|
||||
parso==0.8.4
|
||||
pexpect==4.9.0
|
||||
pillow==10.3.0
|
||||
platformdirs==4.2.0
|
||||
plotly==5.21.0
|
||||
prompt-toolkit==3.0.43
|
||||
psutil==5.9.8
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
pybit==5.6.2
|
||||
pycryptodome==3.20.0
|
||||
pydantic
|
||||
pydantic_core
|
||||
Pygments==2.17.2
|
||||
pyparsing==3.1.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
pytz==2024.1
|
||||
pyzmq==25.1.2
|
||||
rapidfuzz==3.9.0
|
||||
referencing==0.34.0
|
||||
requests==2.31.0
|
||||
rpds-py==0.18.0
|
||||
scipy==1.13.0
|
||||
six==1.16.0
|
||||
sniffio==1.3.1
|
||||
soupsieve==2.5
|
||||
stack-data==0.6.3
|
||||
starlette==0.37.2
|
||||
ta==0.11.0
|
||||
tenacity==8.2.3
|
||||
thefuzz==0.22.1
|
||||
tornado==6.4
|
||||
traitlets==5.14.2
|
||||
typing==3.7.4.3
|
||||
typing_extensions==4.11.0
|
||||
tzdata==2024.1
|
||||
tzlocal==5.2
|
||||
urllib3==2.2.1
|
||||
uvicorn==0.20.0
|
||||
wcwidth==0.2.13
|
||||
websocket-client==1.7.0
|
||||
websockets==12.0
|
||||
yarl==1.9.4
|
||||
3
settings.ini
Normal file
3
settings.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[settings]
|
||||
bot_token=7096948690:AAGe_b8fOKsLSvjK-Yk703JhEN3ySaqucKo
|
||||
admin_id=340394898
|
||||
BIN
tgbot/.DS_Store
vendored
Normal file
BIN
tgbot/.DS_Store
vendored
Normal file
Binary file not shown.
1
tgbot/__init__.py
Normal file
1
tgbot/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
||||
0
tgbot/data/__init__.py
Normal file
0
tgbot/data/__init__.py
Normal file
42
tgbot/data/config.py
Normal file
42
tgbot/data/config.py
Normal file
@ -0,0 +1,42 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import configparser
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
# Токен бота
|
||||
BOT_TOKEN = configparser.ConfigParser()
|
||||
BOT_TOKEN.read("settings.ini")
|
||||
BOT_TOKEN = BOT_TOKEN['settings']['bot_token'].strip().replace(' ', '')
|
||||
BOT_TIMEZONE = "Asia/Krasnoyarsk" # Временная зона бота
|
||||
|
||||
# Пути к файлам
|
||||
PATH_DATABASE = "tgbot/data/database.db" # Путь к БД
|
||||
PATH_LOGS = "tgbot/data/logs.log" # Путь к Логам
|
||||
PATH_EXCEL = "tgbot/data/articles_sheet.xlsx" # Путь к таблице с артиклами
|
||||
|
||||
# Образы и конфиги
|
||||
BOT_SCHEDULER = AsyncIOScheduler(timezone=BOT_TIMEZONE) # Образ шедулера
|
||||
start_status = True # Оповещение админам о запуске бота (True или False)
|
||||
|
||||
|
||||
# Получение администраторов бота
|
||||
def get_admins() -> list[int]:
|
||||
read_admins = configparser.ConfigParser()
|
||||
read_admins.read('settings.ini')
|
||||
|
||||
admins = read_admins['settings']['admin_id'].strip().replace(" ", "")
|
||||
|
||||
if "," in admins:
|
||||
admins = admins.split(",")
|
||||
else:
|
||||
if len(admins) >= 1:
|
||||
admins = [admins]
|
||||
else:
|
||||
admins = []
|
||||
|
||||
while "" in admins: admins.remove("")
|
||||
while " " in admins: admins.remove(" ")
|
||||
while "," in admins: admins.remove(",")
|
||||
while "\r" in admins: admins.remove("\r")
|
||||
|
||||
return list(map(int, admins))
|
||||
0
tgbot/database/__init__.py
Normal file
0
tgbot/database/__init__.py
Normal file
124
tgbot/database/db_helper.py
Normal file
124
tgbot/database/db_helper.py
Normal file
@ -0,0 +1,124 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import sqlite3
|
||||
|
||||
from tgbot.data.config import PATH_DATABASE
|
||||
from tgbot.utils.const_functions import ded
|
||||
|
||||
|
||||
# Преобразование полученного списка в словарь
|
||||
def dict_factory(cursor, row) -> dict:
|
||||
save_dict = {}
|
||||
|
||||
for idx, col in enumerate(cursor.description):
|
||||
save_dict[col[0]] = row[idx]
|
||||
|
||||
return save_dict
|
||||
|
||||
|
||||
# Форматирование запроса без аргументов
|
||||
def update_format(sql, parameters: dict) -> tuple[str, list]:
|
||||
values = ", ".join([
|
||||
f"{item} = ?" for item in parameters
|
||||
])
|
||||
sql += f" {values}"
|
||||
|
||||
return sql, list(parameters.values())
|
||||
|
||||
|
||||
# Форматирование запроса с аргументами
|
||||
def update_format_where(sql, parameters: dict) -> tuple[str, list]:
|
||||
sql += " WHERE "
|
||||
|
||||
sql += " AND ".join([
|
||||
f"{item} = ?" for item in parameters
|
||||
])
|
||||
|
||||
return sql, list(parameters.values())
|
||||
|
||||
|
||||
################################################################################
|
||||
# Создание всех таблиц для БД
|
||||
def create_dbx():
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
|
||||
############################################################
|
||||
# Создание таблицы с хранением - пользователей
|
||||
if len(con.execute("PRAGMA table_info(storage_users)").fetchall()) == 8:
|
||||
print("DB was found(1/4)")
|
||||
else:
|
||||
con.execute(
|
||||
ded(f"""
|
||||
CREATE TABLE storage_users(
|
||||
increment INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
user_login TEXT,
|
||||
user_name TEXT,
|
||||
user_surname TEXT,
|
||||
user_fullname TEXT,
|
||||
notif TEXT,
|
||||
user_unix INTEGER
|
||||
)
|
||||
""")
|
||||
)
|
||||
print("DB was not found(1/4) | Creating...")
|
||||
|
||||
# Создание таблицы с хранением - настроек
|
||||
if len(con.execute("PRAGMA table_info(storage_settings)").fetchall()) == 2: #!!!!!
|
||||
print("DB was found(2/4)")
|
||||
else:
|
||||
con.execute(
|
||||
ded(f"""
|
||||
CREATE TABLE storage_settings(
|
||||
status_work TEXT,
|
||||
status_sched_tenders TEXT
|
||||
)
|
||||
""")
|
||||
)
|
||||
|
||||
con.execute(
|
||||
ded(f"""
|
||||
INSERT INTO storage_settings(
|
||||
status_work,
|
||||
status_sched_tenders
|
||||
)
|
||||
VALUES (?,?)
|
||||
"""),
|
||||
[
|
||||
'True',
|
||||
'True'
|
||||
]
|
||||
)
|
||||
print("DB was not found(2/4) | Creating...")
|
||||
|
||||
# Создание таблицы с хранением - тендеров
|
||||
if len(con.execute("PRAGMA table_info(storage_tenders)").fetchall()) == 5:
|
||||
print("DB was found(3/4)")
|
||||
else:
|
||||
con.execute(
|
||||
ded(f"""
|
||||
CREATE TABLE storage_tenders(
|
||||
tender_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tender_name TEXT,
|
||||
tender_link TEXT,
|
||||
date_creat DATE,
|
||||
date_until DATE
|
||||
)
|
||||
""")
|
||||
)
|
||||
print("DB was not found(3/4) | Creating...")
|
||||
|
||||
# Создание таблицы с хранением - товаров
|
||||
if len(con.execute("PRAGMA table_info(storage_goods)").fetchall()) == 3:
|
||||
print("DB was found(4/4)")
|
||||
else:
|
||||
con.execute(
|
||||
ded(f"""
|
||||
CREATE TABLE storage_goods(
|
||||
good_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
good_name TEXT,
|
||||
tender_id INTEGER REFERENCES storage_tenders(tender_id) ON UPDATE CASCADE
|
||||
)
|
||||
""")
|
||||
)
|
||||
print("DB was not found(4/4) | Creating...")
|
||||
37
tgbot/database/db_settings.py
Normal file
37
tgbot/database/db_settings.py
Normal file
@ -0,0 +1,37 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import sqlite3
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from tgbot.data.config import PATH_DATABASE
|
||||
from tgbot.database.db_helper import dict_factory, update_format
|
||||
|
||||
|
||||
# Модель таблицы
|
||||
class SettingsModel(BaseModel):
|
||||
status_work: str
|
||||
status_sched_tenders: str
|
||||
|
||||
|
||||
# Работа с настройками
|
||||
class Settingsx:
|
||||
storage_name = "storage_settings"
|
||||
|
||||
# Получение записи
|
||||
@staticmethod
|
||||
def get() -> SettingsModel:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Settingsx.storage_name}"
|
||||
|
||||
return SettingsModel(**con.execute(sql).fetchone())
|
||||
|
||||
# Редактирование записи
|
||||
@staticmethod
|
||||
def update(**kwargs):
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"UPDATE {Settingsx.storage_name} SET"
|
||||
sql, parameters = update_format(sql, kwargs)
|
||||
|
||||
con.execute(sql, parameters)
|
||||
137
tgbot/database/db_tenders.py
Normal file
137
tgbot/database/db_tenders.py
Normal file
@ -0,0 +1,137 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import sqlite3
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from tgbot.data.config import PATH_DATABASE
|
||||
from tgbot.database.db_helper import dict_factory, update_format_where, update_format
|
||||
from tgbot.utils.const_functions import get_unix, ded
|
||||
|
||||
|
||||
# Модель таблицы
|
||||
class UserModel(BaseModel):
|
||||
increment: int
|
||||
user_id: int
|
||||
user_login: str
|
||||
user_name: str
|
||||
user_surname: str
|
||||
user_fullname: str
|
||||
notif: str
|
||||
user_unix: int
|
||||
|
||||
|
||||
# Работа с юзером
|
||||
class Userx:
|
||||
storage_name = "storage_users"
|
||||
|
||||
# Добавление записи
|
||||
@staticmethod
|
||||
def add(
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str,
|
||||
user_fullname: str,
|
||||
notif: str = "False",
|
||||
):
|
||||
user_unix = get_unix()
|
||||
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
|
||||
con.execute(
|
||||
ded(f"""
|
||||
INSERT INTO {Userx.storage_name} (
|
||||
user_id,
|
||||
user_login,
|
||||
user_name,
|
||||
user_surname,
|
||||
user_fullname,
|
||||
notif,
|
||||
user_unix
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""),
|
||||
[
|
||||
user_id,
|
||||
user_login,
|
||||
user_name,
|
||||
user_surname,
|
||||
user_fullname,
|
||||
notif,
|
||||
user_unix,
|
||||
],
|
||||
)
|
||||
|
||||
# Получение записи
|
||||
@staticmethod
|
||||
def get(**kwargs) -> UserModel:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
response = con.execute(sql, parameters).fetchone()
|
||||
|
||||
if response is not None:
|
||||
response = UserModel(**response)
|
||||
|
||||
return response
|
||||
|
||||
# Получение записей
|
||||
@staticmethod
|
||||
def gets(**kwargs) -> list[UserModel]:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
response = con.execute(sql, parameters).fetchall()
|
||||
|
||||
if len(response) >= 1:
|
||||
response = [UserModel(**cache_object) for cache_object in response]
|
||||
|
||||
return response
|
||||
|
||||
# Получение всех записей
|
||||
@staticmethod
|
||||
def get_all() -> list[UserModel]:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
|
||||
response = con.execute(sql).fetchall()
|
||||
|
||||
if len(response) >= 1:
|
||||
response = [UserModel(**cache_object) for cache_object in response]
|
||||
|
||||
return response
|
||||
|
||||
# Редактирование записи
|
||||
@staticmethod
|
||||
def update(user_id, **kwargs):
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"UPDATE {Userx.storage_name} SET"
|
||||
sql, parameters = update_format(sql, kwargs)
|
||||
parameters.append(user_id)
|
||||
|
||||
con.execute(sql + "WHERE user_id = ?", parameters)
|
||||
|
||||
# Удаление записи
|
||||
@staticmethod
|
||||
def delete(**kwargs):
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"DELETE FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
con.execute(sql, parameters)
|
||||
|
||||
# Очистка всех записей
|
||||
@staticmethod
|
||||
def clear():
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"DELETE FROM {Userx.storage_name}"
|
||||
|
||||
con.execute(sql)
|
||||
137
tgbot/database/db_users.py
Normal file
137
tgbot/database/db_users.py
Normal file
@ -0,0 +1,137 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import sqlite3
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from tgbot.data.config import PATH_DATABASE
|
||||
from tgbot.database.db_helper import dict_factory, update_format_where, update_format
|
||||
from tgbot.utils.const_functions import get_unix, ded
|
||||
|
||||
|
||||
# Модель таблицы
|
||||
class UserModel(BaseModel):
|
||||
increment: int
|
||||
user_id: int
|
||||
user_login: str
|
||||
user_name: str
|
||||
user_surname: str
|
||||
user_fullname: str
|
||||
notif: str
|
||||
user_unix: int
|
||||
|
||||
|
||||
# Работа с юзером
|
||||
class Userx:
|
||||
storage_name = "storage_users"
|
||||
|
||||
# Добавление записи
|
||||
@staticmethod
|
||||
def add(
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str,
|
||||
user_fullname: str,
|
||||
notif: str = "False",
|
||||
):
|
||||
user_unix = get_unix()
|
||||
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
|
||||
con.execute(
|
||||
ded(f"""
|
||||
INSERT INTO {Userx.storage_name} (
|
||||
user_id,
|
||||
user_login,
|
||||
user_name,
|
||||
user_surname,
|
||||
user_fullname,
|
||||
notif,
|
||||
user_unix
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""),
|
||||
[
|
||||
user_id,
|
||||
user_login,
|
||||
user_name,
|
||||
user_surname,
|
||||
user_fullname,
|
||||
notif,
|
||||
user_unix,
|
||||
],
|
||||
)
|
||||
|
||||
# Получение записи
|
||||
@staticmethod
|
||||
def get(**kwargs) -> UserModel:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
response = con.execute(sql, parameters).fetchone()
|
||||
|
||||
if response is not None:
|
||||
response = UserModel(**response)
|
||||
|
||||
return response
|
||||
|
||||
# Получение записей
|
||||
@staticmethod
|
||||
def gets(**kwargs) -> list[UserModel]:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
response = con.execute(sql, parameters).fetchall()
|
||||
|
||||
if len(response) >= 1:
|
||||
response = [UserModel(**cache_object) for cache_object in response]
|
||||
|
||||
return response
|
||||
|
||||
# Получение всех записей
|
||||
@staticmethod
|
||||
def get_all() -> list[UserModel]:
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"SELECT * FROM {Userx.storage_name}"
|
||||
|
||||
response = con.execute(sql).fetchall()
|
||||
|
||||
if len(response) >= 1:
|
||||
response = [UserModel(**cache_object) for cache_object in response]
|
||||
|
||||
return response
|
||||
|
||||
# Редактирование записи
|
||||
@staticmethod
|
||||
def update(user_id, **kwargs):
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"UPDATE {Userx.storage_name} SET"
|
||||
sql, parameters = update_format(sql, kwargs)
|
||||
parameters.append(user_id)
|
||||
|
||||
con.execute(sql + "WHERE user_id = ?", parameters)
|
||||
|
||||
# Удаление записи
|
||||
@staticmethod
|
||||
def delete(**kwargs):
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"DELETE FROM {Userx.storage_name}"
|
||||
sql, parameters = update_format_where(sql, kwargs)
|
||||
|
||||
con.execute(sql, parameters)
|
||||
|
||||
# Очистка всех записей
|
||||
@staticmethod
|
||||
def clear():
|
||||
with sqlite3.connect(PATH_DATABASE) as con:
|
||||
con.row_factory = dict_factory
|
||||
sql = f"DELETE FROM {Userx.storage_name}"
|
||||
|
||||
con.execute(sql)
|
||||
0
tgbot/keyboards/__init__.py
Normal file
0
tgbot/keyboards/__init__.py
Normal file
26
tgbot/keyboards/inline_main.py
Normal file
26
tgbot/keyboards/inline_main.py
Normal file
@ -0,0 +1,26 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
from tgbot.utils.const_functions import ikb
|
||||
|
||||
|
||||
# Кнопки инлайн меню
|
||||
def menu_finl(user_id: int) -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
# keyboard.row(
|
||||
# ikb("User X", data="user_inline_x"),
|
||||
# ikb("User 1", data="user_inline:user_btn"),
|
||||
# ikb("User 2", data="..."),
|
||||
# )
|
||||
|
||||
# if user_id in get_admins():
|
||||
# keyboard.row(
|
||||
# ikb("Admin X", data="admin_inline_x"),
|
||||
# ikb("Admin 1", data="admin_inline:admin_btn"),
|
||||
# ikb("Admin 2", data="unknown"),
|
||||
# )
|
||||
|
||||
return keyboard.as_markup()
|
||||
22
tgbot/keyboards/inline_misc.py
Normal file
22
tgbot/keyboards/inline_misc.py
Normal file
@ -0,0 +1,22 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from tgbot.utils.const_functions import ikb
|
||||
|
||||
# Тестовые админ инлайн кнопки
|
||||
admin_inl = InlineKeyboardBuilder(
|
||||
).row(
|
||||
# ikb("Admin Inline 1", data="..."),
|
||||
# ikb("Admin Inline 2", data="..."),
|
||||
).row(
|
||||
# ikb("Admin Inline 3", data="..."),
|
||||
).as_markup()
|
||||
|
||||
# Тестовые юзер инлайн кнопки
|
||||
user_inl = InlineKeyboardBuilder(
|
||||
).row(
|
||||
# ikb("User Inline 1", data="..."),
|
||||
# ikb("User Inline 2", data="..."),
|
||||
).row(
|
||||
# ikb("User Inline 3", data="..."),
|
||||
).as_markup()
|
||||
49
tgbot/keyboards/reply_main.py
Normal file
49
tgbot/keyboards/reply_main.py
Normal file
@ -0,0 +1,49 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.types import ReplyKeyboardMarkup
|
||||
from aiogram.utils.keyboard import ReplyKeyboardBuilder
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
from tgbot.utils.const_functions import rkb
|
||||
|
||||
|
||||
# Кнопки главного меню
|
||||
def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
# BotCommand(command="start", description="♻️ Restart bot"),
|
||||
# BotCommand(command="parser", description="Запускает поиск тендоров"),
|
||||
# BotCommand(command="status", description="Статус бота"),
|
||||
# BotCommand(command="get_notif", description="Получать уведомления"),
|
||||
# BotCommand(command="stop_get", description="Остановить получение уведомлений"),
|
||||
# BotCommand(command="start_shed", description="Запуск работы по расписанию"),
|
||||
# BotCommand(command="stop_shed", description="Остановка работы по расписанию"),
|
||||
# # BotCommand(command="inline", description="🌀 Get Inline keyboard"),
|
||||
# BotCommand(command="log", description="🖨 Get Logs"),
|
||||
# BotCommand(command="db", description="📦 Get Database"),
|
||||
|
||||
keyboard.row(
|
||||
rkb("Поиск в tenderpro"), rkb("Tendrepro поиск за все время "),
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
rkb("Поиск в tenderplan"), rkb("Показать tenderplan"),
|
||||
)
|
||||
|
||||
# keyboard.row(
|
||||
# rkb("Поиск в tenderplan"),
|
||||
# )
|
||||
|
||||
keyboard.row(
|
||||
rkb("Получать уведомления"), rkb("Не получать уведомления"), rkb("Показать таблицу"), rkb("Статус бота"), rkb("Показать автопитер"),
|
||||
)
|
||||
|
||||
# keyboard.row(
|
||||
# rkb("Показать таблицу"), rkb("Статус бота"),
|
||||
# )
|
||||
|
||||
# if user_id in get_admins():
|
||||
# keyboard.row(
|
||||
# rkb("Admin Inline"), rkb("Admin Reply"),
|
||||
# )
|
||||
|
||||
return keyboard.as_markup(resize_keyboard=True)
|
||||
22
tgbot/keyboards/reply_misc.py
Normal file
22
tgbot/keyboards/reply_misc.py
Normal file
@ -0,0 +1,22 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.utils.keyboard import ReplyKeyboardBuilder
|
||||
|
||||
from tgbot.utils.const_functions import rkb
|
||||
|
||||
# Тестовые админ реплай кнопки
|
||||
admin_rep = ReplyKeyboardBuilder(
|
||||
).row(
|
||||
# rkb("Admin Reply 1"),
|
||||
# rkb("Admin Reply 2"),
|
||||
).row(
|
||||
# rkb("🔙 Main menu"),
|
||||
).as_markup(resize_keyboard=True)
|
||||
|
||||
# Тестовые юзер реплай кнопки
|
||||
user_rep = ReplyKeyboardBuilder(
|
||||
).row(
|
||||
# rkb("User Reply 1"),
|
||||
# rkb("User Reply 2"),
|
||||
).row(
|
||||
# rkb("🔙 Main menu"),
|
||||
).as_markup(resize_keyboard=True)
|
||||
13
tgbot/middlewares/__init__.py
Normal file
13
tgbot/middlewares/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Dispatcher
|
||||
|
||||
from tgbot.middlewares.middleware_user import ExistsUserMiddleware
|
||||
from tgbot.middlewares.middleware_throttling import ThrottlingMiddleware
|
||||
|
||||
|
||||
# Регистрация всех миддлварей
|
||||
def register_all_middlwares(dp: Dispatcher):
|
||||
dp.callback_query.outer_middleware(ExistsUserMiddleware())
|
||||
dp.message.outer_middleware(ExistsUserMiddleware())
|
||||
|
||||
dp.message.middleware(ThrottlingMiddleware())
|
||||
63
tgbot/middlewares/middleware_throttling.py
Normal file
63
tgbot/middlewares/middleware_throttling.py
Normal file
@ -0,0 +1,63 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import time
|
||||
from typing import Any, Awaitable, Callable, Dict, Union
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.dispatcher.flags import get_flag
|
||||
from aiogram.types import Message, User
|
||||
from cachetools import TTLCache
|
||||
|
||||
|
||||
# Антиспам
|
||||
class ThrottlingMiddleware(BaseMiddleware):
|
||||
def __init__(self, default_rate: Union[int, float] = 1) -> None:
|
||||
self.default_rate = default_rate
|
||||
|
||||
self.users = TTLCache(maxsize=10_000, ttl=600)
|
||||
|
||||
async def __call__(self, handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]], event: Message, data):
|
||||
this_user: User = data.get("event_from_user")
|
||||
|
||||
if get_flag(data, "rate") is not None:
|
||||
self.default_rate = get_flag(data, "rate")
|
||||
|
||||
if self.default_rate == 0:
|
||||
return await handler(event, data)
|
||||
|
||||
if this_user.id not in self.users:
|
||||
self.users[this_user.id] = {
|
||||
'last_throttled': int(time.time()),
|
||||
'count_throttled': 0,
|
||||
'now_rate': self.default_rate,
|
||||
}
|
||||
|
||||
return await handler(event, data)
|
||||
else:
|
||||
if int(time.time()) - self.users[this_user.id]['last_throttled'] >= self.users[this_user.id]['now_rate']:
|
||||
self.users.pop(this_user.id)
|
||||
|
||||
return await handler(event, data)
|
||||
else:
|
||||
self.users[this_user.id]['last_throttled'] = int(time.time())
|
||||
|
||||
if self.users[this_user.id]['count_throttled'] == 0:
|
||||
self.users[this_user.id]['count_throttled'] = 1
|
||||
self.users[this_user.id]['now_rate'] = self.default_rate + 2
|
||||
|
||||
return await handler(event, data)
|
||||
elif self.users[this_user.id]['count_throttled'] == 1:
|
||||
self.users[this_user.id]['count_throttled'] = 2
|
||||
self.users[this_user.id]['now_rate'] = self.default_rate + 3
|
||||
|
||||
await event.reply(
|
||||
"<b>❗ Пожалуйста, не спамьте.\n"
|
||||
"❗ Please, do not spam.</b>",
|
||||
)
|
||||
elif self.users[this_user.id]['count_throttled'] == 2:
|
||||
self.users[this_user.id]['count_throttled'] = 3
|
||||
self.users[this_user.id]['now_rate'] = self.default_rate + 5
|
||||
|
||||
await event.reply(
|
||||
"<b>❗ Бот не будет отвечать до прекращения спама.\n"
|
||||
"❗ The bot will not respond until the spam stops.</b>",
|
||||
)
|
||||
48
tgbot/middlewares/middleware_user.py
Normal file
48
tgbot/middlewares/middleware_user.py
Normal file
@ -0,0 +1,48 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import BaseMiddleware
|
||||
|
||||
from tgbot.database.db_users import Userx
|
||||
from tgbot.utils.const_functions import clear_html
|
||||
|
||||
|
||||
# Проверка юзера в БД и его добавление
|
||||
class ExistsUserMiddleware(BaseMiddleware):
|
||||
async def __call__(self, handler, event, data):
|
||||
this_user = data.get("event_from_user")
|
||||
|
||||
if not this_user.is_bot:
|
||||
get_user = Userx.get(user_id=this_user.id)
|
||||
|
||||
user_id = this_user.id
|
||||
user_login = this_user.username
|
||||
user_name = clear_html(this_user.first_name)
|
||||
user_surname = clear_html(this_user.last_name)
|
||||
user_fullname = clear_html(this_user.first_name)
|
||||
user_language = this_user.language_code
|
||||
|
||||
if user_login is None: user_login = ""
|
||||
if user_name is None: user_name = ""
|
||||
if user_surname is None: user_surname = ""
|
||||
if user_fullname is None: user_fullname = ""
|
||||
if user_language != "ru": user_language = "en"
|
||||
|
||||
if len(user_surname) >= 1: user_fullname += f" {user_surname}"
|
||||
|
||||
if get_user is None:
|
||||
Userx.add(user_id, user_login.lower(), user_name, user_surname, user_fullname)
|
||||
else:
|
||||
if user_name != get_user.user_name:
|
||||
Userx.update(get_user.user_id, user_name=user_name)
|
||||
|
||||
if user_surname != get_user.user_surname:
|
||||
Userx.update(get_user.user_id, user_surname=user_surname)
|
||||
|
||||
if user_fullname != get_user.user_fullname:
|
||||
Userx.update(get_user.user_id, user_fullname=user_fullname)
|
||||
|
||||
if user_login.lower() != get_user.user_login:
|
||||
Userx.update(get_user.user_id, user_login=user_login.lower())
|
||||
|
||||
data['User'] = Userx.get(user_id=user_id)
|
||||
|
||||
return await handler(event, data)
|
||||
BIN
tgbot/routers/.DS_Store
vendored
Normal file
BIN
tgbot/routers/.DS_Store
vendored
Normal file
Binary file not shown.
30
tgbot/routers/__init__.py
Normal file
30
tgbot/routers/__init__.py
Normal file
@ -0,0 +1,30 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Dispatcher, F
|
||||
|
||||
from tgbot.routers import main_errors, main_missed, main_start
|
||||
from tgbot.routers.admin import admin_menu
|
||||
from tgbot.routers.user import user_menu
|
||||
from tgbot.utils.misc.bot_filters import IsAdmin
|
||||
|
||||
|
||||
# Регистрация всех роутеров
|
||||
def register_all_routers(dp: Dispatcher):
|
||||
# Подключение фильтров
|
||||
main_errors.router.message.filter(F.chat.type == "private")
|
||||
main_start.router.message.filter(F.chat.type == "private")
|
||||
|
||||
user_menu.router.message.filter(F.chat.type == "private")
|
||||
admin_menu.router.message.filter(F.chat.type == "private", IsAdmin())
|
||||
|
||||
main_missed.router.message.filter(F.chat.type == "private")
|
||||
|
||||
# Подключение обязательных роутеров
|
||||
dp.include_router(main_errors.router) # Роутер ошибки
|
||||
dp.include_router(main_start.router) # Роутер основных команд
|
||||
|
||||
# Подключение пользовательских роутеров (юзеров и админов)
|
||||
dp.include_router(user_menu.router) # Юзер роутер
|
||||
dp.include_router(admin_menu.router) # Админ роутер
|
||||
|
||||
# Подключение обязательных роутеров
|
||||
dp.include_router(main_missed.router) # Роутер пропущенных апдейтов
|
||||
0
tgbot/routers/admin/__init__.py
Normal file
0
tgbot/routers/admin/__init__.py
Normal file
121
tgbot/routers/admin/admin_menu.py
Normal file
121
tgbot/routers/admin/admin_menu.py
Normal file
@ -0,0 +1,121 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import os
|
||||
|
||||
import aiofiles
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import FSInputFile, Message, CallbackQuery
|
||||
from aiogram.utils.media_group import MediaGroupBuilder
|
||||
|
||||
from tgbot.data.config import PATH_DATABASE, PATH_LOGS
|
||||
from tgbot.database.db_users import UserModel
|
||||
from tgbot.keyboards.inline_misc import admin_inl
|
||||
from tgbot.keyboards.reply_misc import admin_rep
|
||||
from tgbot.utils.const_functions import get_date
|
||||
from tgbot.utils.misc_functions import send_employees
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.services.parser_tendors import get_tenders_from_url
|
||||
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
# # start shed
|
||||
# @router.message(F.text.in_(('start_shed')))
|
||||
# @router.message(Command(commands=['start_shed']))
|
||||
# async def parser(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# bot_logger.warning(f"command start_shed from {User.user_name}")
|
||||
|
||||
# await send_employees(bot,"ss")
|
||||
|
||||
# tenders_id = get_tenders_from_url()
|
||||
# bot_logger.warning(f"tenders_id: {tenders_id}")
|
||||
# answ = ""
|
||||
# for num, tend in enumerate(tenders_id):
|
||||
# answ += f"{num+1}. Наименование/артикул: {tend['article']}, id тендера: {tend['id_tender']}, url: {tend['url_tender']} \n \n"
|
||||
|
||||
# await message.answer(answ)
|
||||
|
||||
|
||||
|
||||
# # Кнопка - Admin Inline
|
||||
# @router.message(F.text == 'Admin Inline')
|
||||
# async def admin_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# await message.answer("Click Button - Admin Inline", reply_markup=admin_inl)
|
||||
|
||||
|
||||
# # Кнопка - Admin Reply
|
||||
# @router.message(F.text == 'Admin Reply')
|
||||
# async def admin_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# await message.answer("Click Button - Admin Reply", reply_markup=admin_rep)
|
||||
|
||||
|
||||
# # Колбэк - Admin X
|
||||
# @router.callback_query(F.data == 'admin_inline_x')
|
||||
# async def admin_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await call.answer(f"Click Admin X")
|
||||
|
||||
|
||||
# # Колбэк - Admin
|
||||
# @router.callback_query(F.data.startswith('admin_inline:'))
|
||||
# async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# get_data = call.data.split(":")[1]
|
||||
|
||||
# await call.answer(f"Click Admin - {get_data}", True)
|
||||
|
||||
|
||||
# Получение Базы Данных
|
||||
@router.message(Command(commands=['db', 'database']))
|
||||
async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
|
||||
await message.answer_document(
|
||||
FSInputFile(PATH_DATABASE),
|
||||
caption=f"<b>📦 #BACKUP | <code>{get_date()}</code></b>",
|
||||
)
|
||||
|
||||
|
||||
# # Получение логов
|
||||
# @router.message(Command(commands=['log', 'logs']))
|
||||
# async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# media_group = MediaGroupBuilder(
|
||||
# caption=f"<b>🖨 #LOGS | <code>{get_date(full=False)}</code></b>",
|
||||
# )
|
||||
|
||||
# if os.path.isfile(PATH_LOGS):
|
||||
# media_group.add_document(media=FSInputFile(PATH_LOGS))
|
||||
|
||||
# if os.path.isfile("tgbot/data/sv_log_err.log"):
|
||||
# media_group.add_document(media=FSInputFile("tgbot/data/sv_log_err.log"))
|
||||
|
||||
# if os.path.isfile("tgbot/data/sv_log_out.log"):
|
||||
# media_group.add_document(media=FSInputFile("tgbot/data/sv_log_out.log"))
|
||||
|
||||
# await message.answer_media_group(media=media_group.build())
|
||||
|
||||
|
||||
# Очистить логи
|
||||
@router.message(Command(commands=['clear_log', 'clear_logs', 'log_clear', 'logs_clear']))
|
||||
async def admin_logs_clear(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
|
||||
if os.path.isfile(PATH_LOGS):
|
||||
async with aiofiles.open(PATH_LOGS, "w") as file:
|
||||
await file.write(f"{get_date()} | LOGS WAS CLEAR")
|
||||
|
||||
if os.path.isfile("tgbot/data/sv_log_err.log"):
|
||||
async with aiofiles.open("tgbot/data/sv_log_err.log", "w") as file:
|
||||
await file.write(f"{get_date()} | LOGS WAS CLEAR")
|
||||
|
||||
if os.path.isfile("tgbot/data/sv_log_out.log"):
|
||||
async with aiofiles.open("tgbot/data/sv_log_out.log", "w") as file:
|
||||
await file.write(f"{get_date()} | LOGS WAS CLEAR")
|
||||
|
||||
await message.answer("<b>🖨 The logs have been cleared</b>")
|
||||
29
tgbot/routers/main_errors.py
Normal file
29
tgbot/routers/main_errors.py
Normal file
@ -0,0 +1,29 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Router
|
||||
from aiogram.filters import ExceptionMessageFilter
|
||||
from aiogram.handlers import ErrorHandler
|
||||
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
# Ошибка с блокировкой бота пользователем
|
||||
# @router.errors(ExceptionTypeFilter(TelegramForbiddenError))
|
||||
# class MyHandler(ErrorHandler):
|
||||
# async def handle(self):
|
||||
# pass
|
||||
|
||||
|
||||
# Ошибка с редактированием одинакового сообщения
|
||||
@router.errors(ExceptionMessageFilter(
|
||||
"Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")
|
||||
)
|
||||
class MyHandler(ErrorHandler):
|
||||
async def handle(self):
|
||||
bot_logger.exception(
|
||||
f"====================\n"
|
||||
f"Exception name: {self.exception_name}\n"
|
||||
f"Exception message: {self.exception_message}\n"
|
||||
f"===================="
|
||||
)
|
||||
61
tgbot/routers/main_missed.py
Normal file
61
tgbot/routers/main_missed.py
Normal file
@ -0,0 +1,61 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import io
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.types import CallbackQuery, Message, BufferedInputFile
|
||||
import aiogram
|
||||
import pandas as pd
|
||||
from tgbot.database.db_users import UserModel
|
||||
from tgbot.utils.const_functions import del_message
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
|
||||
from tgbot.services.parser_tendors import get_tenders_from_article, get_excel_from_tenders
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
# Колбэк с удалением сообщения
|
||||
@router.callback_query(F.data == 'close_this')
|
||||
async def main_callback_close(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await del_message(call.message)
|
||||
|
||||
|
||||
# Колбэк с обработкой кнопки
|
||||
@router.callback_query(F.data == '...')
|
||||
async def main_callback_answer(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await call.answer(cache_time=30)
|
||||
|
||||
|
||||
# Колбэк с обработкой удаления сообщений потерявших стейт
|
||||
@router.callback_query()
|
||||
async def main_callback_missed(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await call.answer(f"❗️ Miss callback: {call.data}", True)
|
||||
|
||||
|
||||
# Обработка всех неизвестных команд
|
||||
@router.message()
|
||||
async def main_message_missed(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
try:
|
||||
tenders = await get_tenders_from_article(message.text)
|
||||
if (len(str(tenders))>2000):
|
||||
tenders = pd.DataFrame(tenders)
|
||||
get_excel_from_tenders(tenders)
|
||||
with io.BytesIO() as output:
|
||||
tenders.to_excel(output)
|
||||
excel_data = output.getvalue()
|
||||
file_excel = io.BytesIO(excel_data)
|
||||
await message.answer_document(BufferedInputFile(file_excel.getvalue(), f"{message.text}.xlsx"), caption = f"Нашлось по запросу '{message.text}'")
|
||||
else:
|
||||
answ = ""
|
||||
for num, tend in enumerate(tenders):
|
||||
answ += f"{num+1}. Наименование/артикул: {tend['article']}, id тендера: {tend['id_tender']}, прием до: {tend['date_until']}, url: {tend['url_tender']} \n"
|
||||
mes = f"Нашлось по запросу {message.text}: \n"
|
||||
if answ == "":
|
||||
mes += "Ничего не найдено"
|
||||
else:
|
||||
mes += answ
|
||||
await message.answer(f"{mes}")
|
||||
# except aiogram.exceptions.TelegramBadRequest:
|
||||
# await message.answer(f"Ошибка: wg mes too long, {len(str(tenders))}")
|
||||
except Exception as e:
|
||||
await message.answer(f"Ошибка: {e}")
|
||||
|
||||
235
tgbot/routers/main_start.py
Normal file
235
tgbot/routers/main_start.py
Normal file
@ -0,0 +1,235 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import os
|
||||
import io
|
||||
import pandas as pd
|
||||
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import FSInputFile, Message, CallbackQuery, BufferedInputFile
|
||||
|
||||
from aiogram.utils.media_group import MediaGroupBuilder
|
||||
from tgbot.database.db_users import UserModel, Userx
|
||||
from tgbot.keyboards.reply_main import menu_frep
|
||||
from tgbot.utils.const_functions import ded
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.services.parser_tendors import get_tenders_from_url, get_excel_from_tenders, get_articles
|
||||
from tgbot.services.tender_plan import tenders_with_goods, search_in_tenderplan
|
||||
from tgbot.data.config import BOT_SCHEDULER, PATH_EXCEL, PATH_LOGS
|
||||
from tgbot.utils.const_functions import get_date
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
# Открытие главного меню
|
||||
@router.message(F.text.in_(()))
|
||||
@router.message(Command(commands=['start']))
|
||||
async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
|
||||
bot_logger.warning(f"command start from {User.user_name}")
|
||||
await message.answer(
|
||||
ded(f"""
|
||||
Привет, {User.user_name}
|
||||
Это бот для поиска тендеров на сайте Tender.pro
|
||||
Введите /parser для поиска прямо сейчас (ищет долго, минут 10)
|
||||
Или /get_notif для получения уведомления в 8 и 18 часов
|
||||
Команда /status показывает статус бота
|
||||
Поиск за все время выведет таблицу в которой будут тендеры за все время, не важно какой статус
|
||||
Чтобы обновить таблицу по которой искать просто пришлите ее мне
|
||||
"""),
|
||||
reply_markup=menu_frep(message.from_user.id),
|
||||
)
|
||||
|
||||
|
||||
# parser
|
||||
@router.message(F.text.in_(('parser', 'Начать поиск сейчас', 'Поиск в tenderpro')))
|
||||
@router.message(Command(commands=['parser']))
|
||||
async def parser(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
try:
|
||||
bot_logger.warning(f"command parser from {User.user_name}")
|
||||
await message.answer("Идет поиск тендеров")
|
||||
tenders_id = await get_tenders_from_url()
|
||||
bot_logger.warning(f"tenders_id: {tenders_id}")
|
||||
if (len(str(tenders_id))>4000):
|
||||
tenders_id = pd.DataFrame(tenders_id)
|
||||
get_excel_from_tenders(tenders_id)
|
||||
with io.BytesIO() as output:
|
||||
tenders_id.to_excel(output)
|
||||
excel_data = output.getvalue()
|
||||
file_excel = io.BytesIO(excel_data)
|
||||
await message.answer_document(BufferedInputFile(file_excel.getvalue(), f"{message.text}.xlsx"), caption = f"Нашлось по запросу '{message.text}'")
|
||||
else:
|
||||
answ = ""
|
||||
for num, tend in enumerate(tenders_id):
|
||||
answ += f"{num+1}. Наименование/артикул: {tend['article']}, id тендера: {tend['id_tender']}, прием до: {tend['date_until']}, url: {tend['url_tender']} \n \n"
|
||||
mes = f"Ответ на запрос поиска тендеров: \n \n"
|
||||
if answ == "":
|
||||
mes += "Ничего не найдено"
|
||||
else:
|
||||
mes += answ
|
||||
await message.answer(f"{mes}")
|
||||
except Exception as e:
|
||||
await message.answer(f"Ошибка: {e}")
|
||||
|
||||
|
||||
# status
|
||||
@router.message(F.text.in_(('status', 'Статус бота')))
|
||||
@router.message(Command(commands=['status']))
|
||||
async def status(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
bot_logger.warning(f"command status from {User.user_name}")
|
||||
jobs = BOT_SCHEDULER.get_jobs()
|
||||
bot_logger.warning(f"jobs: {jobs}")
|
||||
await message.answer(f"jobs: \n{[str(j) for j in jobs]}")
|
||||
|
||||
# start notification
|
||||
@router.message(F.text.in_(('Получать уведомления', 'get_notif')))
|
||||
@router.message(Command(commands=['get_notif']))
|
||||
async def get_notif(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
bot_logger.warning(f"command get_notif from {User.user_name}")
|
||||
|
||||
if User.notif == "False":
|
||||
Userx.update(User.user_id, notif = "True")
|
||||
await message.answer("Теперь вы будете получать уведомления")
|
||||
|
||||
else:
|
||||
await message.answer("Вы уже получаете уведомления")
|
||||
|
||||
# stop notification
|
||||
@router.message(F.text.in_(('Не получать уведомления', 'stop_get_notif')))
|
||||
@router.message(Command(commands=['stop_get_notif']))
|
||||
async def stop_get_notif(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
bot_logger.warning(f"command stop_get_notif from {User.user_name}")
|
||||
|
||||
if User.notif == "True":
|
||||
Userx.update(User.user_id, notif = "False")
|
||||
await message.answer("Теперь вы не будете получать уведомления")
|
||||
|
||||
else:
|
||||
await message.answer("Вы и так не получаете уведомления")
|
||||
|
||||
|
||||
# Загрузка таблицы в бот
|
||||
@router.message(F.content_type.in_({'document', 'file'}))
|
||||
async def upload_excel(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
if (message.document.file_name.find('.xls') != -1):
|
||||
file_id = message.document.file_id
|
||||
file = await bot.get_file(file_id)
|
||||
file_path = file.file_path
|
||||
if message.document.file_name.find('articles_sheet') != -1:
|
||||
link_temp = "tgbot/data/articles_sheet_temp.xlsx"
|
||||
await bot.download_file(file_path, link_temp)
|
||||
try:
|
||||
arts = get_articles(link=link_temp)
|
||||
bot_logger.warning(f"command upload_excel from {User.user_name}. файл: {message.document.file_name}, загружен")
|
||||
await bot.download_file(file_path, "tgbot/data/articles_sheet.xlsx")
|
||||
await message.answer("Таблица загружена")
|
||||
except Exception as e:
|
||||
bot_logger.warning(f"command upload_excel from {User.user_name}. файл: {message.document.file_name}, не загружен. Ошибка {e}")
|
||||
await message.answer(f"Ошибка {e} \nФайл не был загружен")
|
||||
else:
|
||||
await bot.download_file(file_path, f"tgbot/data/price_{message.document.file_name}")
|
||||
await message.answer(f"Таблица {message.document.file_name} загружена")
|
||||
|
||||
else:
|
||||
bot_logger.warning(f"command upload_excel from {User.user_name}. файл: {message.document.file_name}, не загружен")
|
||||
await message.answer("Файл должен быть с расширением .xls или .xlsx")
|
||||
|
||||
|
||||
# Выгрузка таблицы
|
||||
@router.message(F.text.in_(('Показать таблицу', 'get_articles')))
|
||||
@router.message(Command(commands=['get_sheet', 'article']))
|
||||
async def get_sheet(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
await message.answer_document(
|
||||
FSInputFile(PATH_EXCEL),
|
||||
# caption=f"<b>📦 #BACKUP | <code>{get_date()}</code></b>",
|
||||
caption=f"Таблица, которую вы загрузили и по которой выполняется поиск",
|
||||
)
|
||||
|
||||
# Поиск тендеров за все время
|
||||
@router.message(F.text.in_(('Поиск за все время', 'excel_from_tenders')))
|
||||
@router.message(Command(commands=['excel_from_tenders', 'tenders']))
|
||||
async def excel_from_tenders(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
tenders_id = await get_tenders_from_url(tender_state=100)
|
||||
get_excel_from_tenders(tenders_id=tenders_id)
|
||||
|
||||
await message.answer_document(
|
||||
FSInputFile('tgbot/data/tenders_id_all.xlsx'),
|
||||
# caption=f"<b>📦 #BACKUP | <code>{get_date()}</code></b>",
|
||||
caption=f"Тендеры за все время.",
|
||||
)
|
||||
|
||||
|
||||
# Получение логов
|
||||
@router.message(Command(commands=['log', 'logs']))
|
||||
async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
|
||||
media_group = MediaGroupBuilder(
|
||||
caption=f"<b>🖨 #LOGS | <code>{get_date(full=False)}</code></b>",
|
||||
)
|
||||
|
||||
if os.path.isfile(PATH_LOGS):
|
||||
media_group.add_document(media=FSInputFile(PATH_LOGS))
|
||||
|
||||
if os.path.isfile("tgbot/data/sv_log_err.log"):
|
||||
media_group.add_document(media=FSInputFile("tgbot/data/sv_log_err.log"))
|
||||
|
||||
if os.path.isfile("tgbot/data/sv_log_out.log"):
|
||||
media_group.add_document(media=FSInputFile("tgbot/data/sv_log_out.log"))
|
||||
|
||||
await message.answer_media_group(media=media_group.build())
|
||||
|
||||
|
||||
# Поиск тендеров в автопитере
|
||||
@router.message(F.text.in_(('Поиск в автопитере', 'tenders_with_goods')))
|
||||
@router.message(Command(commands=['tenders_with_goods', 'search_ap']))
|
||||
async def search_in_ap(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
bot_logger.warning(f"command search_in_ap from {User.user_name}")
|
||||
await message.answer("Идет поиск тендеров")
|
||||
tenders_with_goods(1)
|
||||
await message.answer_document(
|
||||
FSInputFile('tgbot/data/tenders_with_goods.xlsx'),
|
||||
caption=f"Тендеры в автопитере.",
|
||||
)
|
||||
|
||||
|
||||
# таблица тендеров в автопитере
|
||||
@router.message(F.text.in_(('Показать автопитер', 'excel_ap')))
|
||||
@router.message(Command(commands=['excel_from_ap', 'show_ap']))
|
||||
async def excel_from_ap(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
bot_logger.warning(f"command excel_from_ap from {User.user_name}")
|
||||
await message.answer_document(
|
||||
FSInputFile('tgbot/data/tenders_with_goods.xlsx'),
|
||||
caption=f"Тендеры в автопитере.",
|
||||
)
|
||||
|
||||
|
||||
# Поиск тендеров в tenderplan
|
||||
@router.message(F.text.in_(('Поиск в tenderplan', 'tenders_in_tenderplan')))
|
||||
@router.message(Command(commands=['tenders_in_tenderplan', 'search_in_tenderplan', 'tenderplan']))
|
||||
async def search_in_tenderplan1(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
bot_logger.warning(f"command search_in_tenderplan from {User.user_name}")
|
||||
await message.answer("Идет поиск тендеров")
|
||||
await search_in_tenderplan()
|
||||
await message.answer_document(
|
||||
FSInputFile('tgbot/data/tenders_tenderplan_from_art.xlsx'),
|
||||
caption=f"Тендеры в tenderplan.",
|
||||
)
|
||||
|
||||
|
||||
# таблица тендеров в tenderplan
|
||||
@router.message(F.text.in_(('Показать tenderplan', 'excel_tenderplan')))
|
||||
@router.message(Command(commands=['excel_from_tenderplan', 'show_tenderplan']))
|
||||
async def excel_from_tenderplan(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
await state.clear()
|
||||
bot_logger.warning(f"command excel_from_tenderplan from {User.user_name}")
|
||||
await message.answer_document(
|
||||
FSInputFile('tgbot/data/tenders_tenderplan_from_art.xlsx'),
|
||||
caption=f"Тендеры в tenderplan.",
|
||||
)
|
||||
0
tgbot/routers/user/__init__.py
Normal file
0
tgbot/routers/user/__init__.py
Normal file
59
tgbot/routers/user/user_menu.py
Normal file
59
tgbot/routers/user/user_menu.py
Normal file
@ -0,0 +1,59 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
|
||||
from tgbot.database.db_users import UserModel
|
||||
from tgbot.keyboards.inline_main import menu_finl
|
||||
from tgbot.keyboards.inline_misc import user_inl
|
||||
from tgbot.keyboards.reply_misc import user_rep
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
# # Кнопка - User Inline
|
||||
# @router.message(F.text == 'User Inline')
|
||||
# async def user_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# await message.answer(
|
||||
# "Click Button - User Inline",
|
||||
# reply_markup=user_inl,
|
||||
# )
|
||||
|
||||
|
||||
# # Кнопка - User Reply
|
||||
# @router.message(F.text == 'User Reply')
|
||||
# async def user_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# await message.answer(
|
||||
# "Click Button - User Reply",
|
||||
# reply_markup=user_rep,
|
||||
# )
|
||||
|
||||
|
||||
# # Команда - /inline
|
||||
# @router.message(Command(commands="inline"))
|
||||
# async def user_command_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await state.clear()
|
||||
|
||||
# await message.answer(
|
||||
# "Click command - /inline",
|
||||
# reply_markup=menu_finl(message.from_user.id),
|
||||
# )
|
||||
|
||||
|
||||
# # Колбэк - User X
|
||||
# @router.callback_query(F.data == 'user_inline_x')
|
||||
# async def user_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# await call.answer(f"Click User X")
|
||||
|
||||
|
||||
# # Колбэк - User
|
||||
# @router.callback_query(F.data.startswith('user_inline:'))
|
||||
# async def user_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
|
||||
# get_data = call.data.split(":")[1]
|
||||
|
||||
# await call.answer(f"Click User - {get_data}", True)
|
||||
0
tgbot/services/__init__.py
Normal file
0
tgbot/services/__init__.py
Normal file
30
tgbot/services/api_session.py
Normal file
30
tgbot/services/api_session.py
Normal file
@ -0,0 +1,30 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
# In handler
|
||||
# session = await arSession.get_session()
|
||||
# response = await session.get(...)
|
||||
# response = await session.post(...)
|
||||
|
||||
# Асинхронная сессия для запросов
|
||||
class AsyncRequestSession:
|
||||
def __init__(self) -> None:
|
||||
self._session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
# Получение сессии
|
||||
async def get_session(self) -> aiohttp.ClientSession:
|
||||
if self._session is None:
|
||||
new_session = aiohttp.ClientSession()
|
||||
self._session = new_session
|
||||
|
||||
return self._session
|
||||
|
||||
# Закрытие сессии
|
||||
async def close(self) -> None:
|
||||
if self._session is None:
|
||||
return None
|
||||
|
||||
await self._session.close()
|
||||
242
tgbot/services/parser_tendors.py
Normal file
242
tgbot/services/parser_tendors.py
Normal file
@ -0,0 +1,242 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import asyncio
|
||||
from aiohttp import ClientSession
|
||||
import pandas as pd
|
||||
from time import time
|
||||
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
from tgbot.data.config import PATH_EXCEL
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
|
||||
|
||||
# arrt = [
|
||||
# {'Name': 'О-514002', 'articles': ['О-514002', 'JX0818', '61000070005', '3831236', 'W 962/6', 'OP592', 'C-6204']},
|
||||
# {'Name': 'F-714117', 'articles': ['F-714117', '612630080087', 'FC-71090', 'R010018']},
|
||||
# {'Name': 'F-742003', 'articles': ['F-742003', '612630080088', 'PL420', 'VG1540080032', 'A 960 477 00 03', 'PL 420/7X', 'SFC-7939-30B']},
|
||||
# {'Name': 'F-742003X', 'articles': ['F-742003X', '612630080088', 'PL420 с подогревом', 'VG1540080032', 'A960 477 00 03','PL 420/7X', 'SFC-7939-30B']},
|
||||
# ]
|
||||
|
||||
# article = {
|
||||
# 'О-514002': ['О-514002', 'JX0818', '61000070005', '3831236', 'W 962/6', 'OP592', 'C-6204'],
|
||||
# 'F-714117': ['F-714117', '612630080087', 'FC-71090', 'R010018'],
|
||||
# 'F-742003': ['F-742003', '612630080088', 'PL420', 'VG1540080032', 'A 960 477 00 03', 'PL 420/7X', 'SFC-7939-30B'],
|
||||
# 'F-742003X': ['F-742003X', '612630080088', 'PL420 с подогревом', 'VG1540080032', 'A960 477 00 03','PL 420/7X', 'SFC-7939-30B'],
|
||||
# }
|
||||
|
||||
# art1 = {
|
||||
# 'F-714117': ['612630080087', 'FC-71090', 'R010018'],
|
||||
# 'F-742003': ['612630080088', 'PL420', 'VG1540080032'],
|
||||
# }
|
||||
|
||||
|
||||
# def get_articles():
|
||||
# link = PATH_EXCEL
|
||||
# art = pd.read_excel(link, skiprows=1)
|
||||
# art = art.loc[:,["Наименование Wanlanda","Наименование аналога"]]
|
||||
# art["Наименование аналога"] = art["Наименование аналога"].str.split(", |,")
|
||||
# for a in art.iloc:
|
||||
# a["Наименование аналога"].append(a["Наименование Wanlanda"])
|
||||
# return art
|
||||
|
||||
def get_articles(link = PATH_EXCEL):
|
||||
# link = PATH_EXCEL
|
||||
all_article = pd.DataFrame(index=[], columns=['Наименование', 'Артикул'])
|
||||
excel_reader = pd.ExcelFile(link)
|
||||
for sheet_name in excel_reader.sheet_names:
|
||||
exc = excel_reader.parse(sheet_name, usecols=['Наименование', 'Артикул'])
|
||||
# exc['Наименование'] = exc['Наименование'].str
|
||||
exc['Наименование'] = sheet_name + " / " + exc['Наименование'].astype(str) # добавление названия листа к наименованию позиции
|
||||
all_article = pd.concat([all_article,exc], ignore_index=True)
|
||||
all_article = all_article.dropna(inplace=False)
|
||||
all_article["Артикул"] = all_article["Артикул"].astype(str)
|
||||
all_article["Артикул"] = all_article["Артикул"].str.split(", | ,")
|
||||
# for a in art.iloc:
|
||||
# a["Артикул"].append(a["Наименование"])
|
||||
# print(f"колич артик:{len(all_article)}")
|
||||
bot_logger.warning(f"колич артик:{len(all_article)}")
|
||||
print(f"all_article: {all_article[:2]}")
|
||||
return all_article[:]
|
||||
|
||||
# для pd
|
||||
def get_urls(tender_state = 1, article = 0):
|
||||
# tender_state = 1:открытые 100:все
|
||||
urls = []
|
||||
if (article == 0):
|
||||
article = get_articles()
|
||||
for val in article.iloc:
|
||||
# print(val["Артикул"])
|
||||
for art in val["Артикул"]:
|
||||
urls.append({"article": f"{val['Наименование']}/{art}", "url": f"http://www.tender.pro/api/tenders/list?&good_name={art}&tender_state={tender_state}&by=1000"})
|
||||
else:
|
||||
articles = article.split(", |,")
|
||||
for art in articles:
|
||||
urls.append({"article": f"{art}", "url": f"http://www.tender.pro/api/tenders/list?&good_name={art}&tender_state={tender_state}&by=1000"})
|
||||
|
||||
return urls
|
||||
|
||||
|
||||
async def fetch(url, session):
|
||||
async with session.get(url['url']) as response:
|
||||
status = response.status
|
||||
date = response.headers.get("DATE")
|
||||
print(f"{date}:{response.url} with status {status}")
|
||||
data = {'url': url, 'response': await response.text()}
|
||||
return data
|
||||
|
||||
|
||||
async def bound_fetch(sem, url, session):
|
||||
# Getter function with semaphore.
|
||||
async with sem:
|
||||
return await fetch(url, session)
|
||||
|
||||
|
||||
# def get_tenders_from_url1():
|
||||
# urls = get_urls()
|
||||
# tenders_id = []
|
||||
# for url in urls:
|
||||
# response = requests.get(url["url"])
|
||||
# soup = BeautifulSoup(response.content, "html.parser")
|
||||
# for tender in soup.find_all("td", class_="tender__id"):
|
||||
# id_tender = tender.text
|
||||
# print(id_tender + str(url["article"]))
|
||||
# tenders_id.append({"article": url["article"], "id_tender": id_tender, "url_tender": f"https://www.tender.pro/api/tender/{id_tender}/view_public"})
|
||||
# return tenders_id
|
||||
|
||||
|
||||
async def get_tenders_from_url(tender_state = 1):
|
||||
urls = get_urls(tender_state)
|
||||
return await search_tenders(urls)
|
||||
|
||||
async def get_tenders_from_article(article):
|
||||
urls = get_urls(article = article)
|
||||
return await search_tenders(urls)
|
||||
|
||||
|
||||
def sooup(soup, tenders_id, res):
|
||||
for tender in soup.find_all("tr", class_="table-stat__row"):
|
||||
try:
|
||||
id_tender = tender.find("td", class_="tender__id").text
|
||||
date_tender = tender.find("td", class_="tender__untill").text
|
||||
except Exception:
|
||||
continue
|
||||
# if id_tender == None:
|
||||
# continue
|
||||
# id_tender = id_tender.text
|
||||
for id in tenders_id:
|
||||
if id_tender in id["id_tender"]:
|
||||
print("ПОВТОРЕНИЕ")
|
||||
return tenders_id
|
||||
print(id_tender, date_tender)
|
||||
# resp = requests.get(f"http://www.tender.pro/api/_tender.item.json?_key=1732ede4de680a0c93d81f01d7bac7d1&company_id=1&id={id_tender}")
|
||||
# try:
|
||||
# goods = resp.json().get("result").get("data")
|
||||
# goods_name = ""
|
||||
# goods_amount = 0
|
||||
# for g in goods:
|
||||
# name = g.get("name")
|
||||
# amount = float(g.get("amount"))
|
||||
# if res['url']['article'].split("/")[1] in name:
|
||||
# goods_name += name + " "
|
||||
# goods_amount += amount
|
||||
tenders_id.append({
|
||||
"article": res['url']['article'],
|
||||
"id_tender": id_tender,
|
||||
"date_until": date_tender,
|
||||
"url_tender": f"https://www.tender.pro/api/tender/{id_tender}/view_public",
|
||||
# "goods_name": goods_name,
|
||||
# "goods_amount": goods_amount,
|
||||
})
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# pass
|
||||
return tenders_id
|
||||
|
||||
|
||||
async def search_tenders(urls):
|
||||
tasks = []
|
||||
# create instance of Semaphore
|
||||
sem = asyncio.Semaphore(5)
|
||||
results = []
|
||||
t = time()
|
||||
# Create client session that will ensure we dont open new connection
|
||||
# per each request.
|
||||
async with ClientSession() as session:
|
||||
for url in urls:
|
||||
# pass Semaphore and session to every GET request
|
||||
task = asyncio.ensure_future(bound_fetch(sem, url, session))
|
||||
tasks.append(task)
|
||||
|
||||
responses = asyncio.gather(*tasks)
|
||||
results = await responses
|
||||
print(time()-t)
|
||||
print(len(results))
|
||||
t1 = time()
|
||||
tenders_id = []
|
||||
for res in results:
|
||||
soup = BeautifulSoup(res["response"], "html.parser")
|
||||
pag = soup.find("div", class_="pagination-pages")
|
||||
print(f"pag: {pag}")
|
||||
if (pag != None):
|
||||
print(f"pag: {str(pag)[44:45]}")
|
||||
pages = int(str(pag)[44:45])
|
||||
urls2 = []
|
||||
for i in range(pages):
|
||||
urls2.append(f"{res['url']['url']}&page={i}")
|
||||
print(urls2)
|
||||
for ur in urls2:
|
||||
response = requests.get(ur)
|
||||
soup1 = BeautifulSoup(response.content, "html.parser")
|
||||
tenders_id = sooup(soup1, tenders_id, res)
|
||||
tenders_id = sooup(soup, tenders_id, res)
|
||||
|
||||
print(time() - t1)
|
||||
for tend in tenders_id:
|
||||
print(tend)
|
||||
|
||||
return tenders_id
|
||||
|
||||
def get_excel_from_tenders(tenders_id, link = 'tgbot/data/tenders_id_all.xlsx'):
|
||||
tends = pd.DataFrame(tenders_id)
|
||||
tends.to_excel(link)
|
||||
|
||||
|
||||
|
||||
# https://www.tender.pro/api/tender/876455/view_public
|
||||
|
||||
# urls = get_urls1(article)
|
||||
# tenders_id = get_tenders_from_url(urls)
|
||||
# for tend in tenders_id:
|
||||
# print(tend)
|
||||
|
||||
|
||||
|
||||
# zik@MacBook-Air-Ila парсер % /usr/local/bin/python3 /Users/zik/Documents/Programs/парсер/parser_tendors.py
|
||||
# 876455F-714117/612630080087
|
||||
# 878638F-742003/PL420
|
||||
# {'article': 'F-714117/612630080087', 'id_tender': '876455'}
|
||||
# {'article': 'F-742003/PL420', 'id_tender': '878638'}
|
||||
|
||||
|
||||
# http://www2.tender.pro/api/tenders/list?sid=15932209&company_id=415538&face_id=440662&order=3&tmpl-opts=%22company_id%3A415538%22%2C%22face_id%3A440662%22%2C%22order%3A3%22%2C%22view_tenders_list-tmpl-signup%3A1%22%2C%22filter_reset%3A1%22%2C%22view_tenders_list-tmpl-name%3A%22%2C%22view_tenders_list-tmpl-default%3A%22%2C%22tender_id%3A%22%2C%22tender_name%3A%22%2C%22company_name%3A%22%2C%22good_name%3ASFC-7939-30B%22%2C%22tender_type%3A100%22%2C%22tender_state%3A1%22%2C%22tender_interest_type%3A%22%2C%22tender_invited%3A%22%2C%22country%3A0%22%2C%22region%3A%22%2C%22basis%3A0%22%2C%22tender_show_own%3A0%22%2C%22okved%3A%22%2C%22dateb%3A%22%2C%22datee%3A%22%2C%22dateb2%3A%22%2C%22datee2%3A%22%2C%22by%3A25%22&view_tenders_list-tmpl-signup=1&filter_tmpl=0&filter_reset=1&view_tenders_list-tmpl-name=&view_tenders_list-tmpl-default=&tender_id=&tender_name=&company_name=&good_name=VG1540080032&tender_type=100&tender_state=1&tender_interest_type=&tender_invited=&country=0®ion=&basis=0&tender_show_own=0&okved=&dateb=&datee=&dateb2=&datee2=&by=25
|
||||
# http://www.tender.pro/api/_tender.info.json?_key=1732ede4de680a0c93d81f01d7bac7d1&company_id=44441&id=144276
|
||||
|
||||
# https://www.tender.pro/api/tenders/list?sid=
|
||||
# &company_id=
|
||||
# &face_id=0
|
||||
# &order=3
|
||||
# &tender_id=
|
||||
# &tender_name=
|
||||
# &company_name=
|
||||
# &good_name=PL+420
|
||||
# &tender_type=100
|
||||
# &tender_state=100
|
||||
# &country=0
|
||||
# ®ion=
|
||||
# &basis=0
|
||||
# &okved=
|
||||
# &dateb=&datee=&dateb2=&datee2=
|
||||
|
||||
# PL420
|
||||
|
||||
# https://www.tender.pro/api/tenders/list?&good_name=PL420&tender_state=100
|
||||
588
tgbot/services/tender_plan.py
Normal file
588
tgbot/services/tender_plan.py
Normal file
@ -0,0 +1,588 @@
|
||||
import os
|
||||
import glob
|
||||
import requests
|
||||
import json
|
||||
import pandas as pd
|
||||
import re
|
||||
from thefuzz import fuzz # type: ignore
|
||||
from bs4 import BeautifulSoup
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
||||
from datetime import datetime
|
||||
import time
|
||||
import asyncio
|
||||
from aiohttp import ClientSession
|
||||
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.services.parser_tendors import get_articles
|
||||
|
||||
|
||||
cookies = {
|
||||
'jwt': 's%3ABearer%200b998917d77807264c0b82d9ff64ca6fcefb01437019151eff52a4e706ebcd405099a030761ba0f5c6574aea12c525293c7da298b267c462224502a8c88c5289.p%2FACkoaEjBIK4u5vArNpU3Fh24DqBsyfGcHN8h%2BILig',
|
||||
'referer': 'https://tenderplan.ru/app',
|
||||
'source': 'response_type=code&client_id=619e606a7883684e0e3d10c7&redirect_uri=https%253A%252F%252Fbitrix24.tenderplan.ru%252Ftenderplan%252Foauth&scope=resources%253Aexternal%2520comments%2520marks%253Aread%2520notes%2520relations%253Aread%2520firm%253Aread&state=2f8e5161-d7e0-4e88-9e37-df3f1f75f20a',
|
||||
'__ddg1_': 'ZKa7JlUseYuy3cvawO9W',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Accept': '*/*',
|
||||
'Authorization': 'Bearer 0b998917d77807264c0b82d9ff64ca6fcefb01437019151eff52a4e706ebcd405099a030761ba0f5c6574aea12c525293c7da298b267c462224502a8c88c5289',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'Accept-Language': 'ru',
|
||||
# 'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Host': 'tenderplan.ru',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://tenderplan.ru/app',
|
||||
# 'Cookie': 'jwt=s%3ABearer%200b998917d77807264c0b82d9ff64ca6fcefb01437019151eff52a4e706ebcd405099a030761ba0f5c6574aea12c525293c7da298b267c462224502a8c88c5289.p%2FACkoaEjBIK4u5vArNpU3Fh24DqBsyfGcHN8h%2BILig; referer=https://tenderplan.ru/app; source=response_type=code&client_id=619e606a7883684e0e3d10c7&redirect_uri=https%253A%252F%252Fbitrix24.tenderplan.ru%252Ftenderplan%252Foauth&scope=resources%253Aexternal%2520comments%2520marks%253Aread%2520notes%2520relations%253Aread%2520firm%253Aread&state=2f8e5161-d7e0-4e88-9e37-df3f1f75f20a; __ddg1_=ZKa7JlUseYuy3cvawO9W',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
}
|
||||
|
||||
# def search_in_tenderplan():
|
||||
# https://tenderplan.ru/api/tenders/getlist?q=F-714117
|
||||
# https://tenderplan.ru/api/tenders/getlist?page=1&q=Komatsu
|
||||
# ...
|
||||
|
||||
|
||||
def get_urls(article = 0):
|
||||
urls = []
|
||||
if (article == 0):
|
||||
article = get_articles()
|
||||
for val in article.iloc:
|
||||
# print(val["Артикул"])
|
||||
for art in val["Артикул"]:
|
||||
urls.append({"article": f"{val['Наименование']} / {art}", "art": art, "url": f"https://tenderplan.ru/api/tenders/getlist?isActual=1&q={art}"})
|
||||
else:
|
||||
articles = article.split(", |,")
|
||||
for art in articles:
|
||||
urls.append({"article": f"{art}", "url": f"https://tenderplan.ru/api/tenders/getlist?isActual=1&q={art}"})
|
||||
|
||||
return urls
|
||||
|
||||
|
||||
# async def fetch(url, session):
|
||||
# st = ''
|
||||
# try:
|
||||
# async with session.get(url['url']) as response:
|
||||
# k = 0
|
||||
# st = response.status
|
||||
# while response.status != 200:
|
||||
# time.sleep(5)
|
||||
# print('sleeep')
|
||||
# k += 1
|
||||
# if k > 5: break
|
||||
|
||||
# date = response.headers.get("DATE")
|
||||
# print(f"{date}:{response.url} with status {response.status}")
|
||||
# data = {'url': url, 'response': await response.json()}
|
||||
# return data
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# bot_logger.error(f"st:{st} --- {e}")
|
||||
|
||||
|
||||
# async def bound_fetch(sem, url, session):
|
||||
# # Getter function with semaphore.
|
||||
# async with sem:
|
||||
# return await fetch(url, session)
|
||||
|
||||
async def fetch(url, session, retry_event):
|
||||
st = ''
|
||||
try:
|
||||
async with session.get(url['url']) as response:
|
||||
k = 0
|
||||
st = response.status
|
||||
while response.status == 429:
|
||||
await retry_event.wait() # Ожидаем разрешения продолжения запросов
|
||||
async with session.get(url['url']) as response:
|
||||
st = response.status
|
||||
|
||||
while response.status != 200 and k <= 5:
|
||||
await asyncio.sleep(5) # Асинхронная задержка
|
||||
print('sleeep')
|
||||
k += 1
|
||||
async with session.get(url['url']) as response:
|
||||
st = response.status
|
||||
|
||||
if response.status == 200:
|
||||
date = response.headers.get("DATE")
|
||||
print(f"{date}: {response.url} со статусом {response.status}")
|
||||
data = {'url': url, 'response': await response.json()}
|
||||
return data
|
||||
else:
|
||||
print(f"Не удалось получить {url['url']} после {k} попыток")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"st: {st} --- {e}")
|
||||
|
||||
async def bound_fetch(sem, url, session, retry_event):
|
||||
# Функция получения данных с семафором
|
||||
async with sem:
|
||||
return await fetch(url, session, retry_event)
|
||||
|
||||
|
||||
|
||||
# async def get_tenders_from_url(tender_state = 1):
|
||||
# urls = get_urls(tender_state)
|
||||
# return await search_tenders(urls)
|
||||
|
||||
# async def get_tenders_from_article(article):
|
||||
# urls = get_urls(article = article)
|
||||
# return await search_tenders(urls)
|
||||
|
||||
def sooup(tenders_id, tenders, res):
|
||||
for tender in tenders:
|
||||
tend_name = tender.get('orderName')
|
||||
tend_id = tender.get('_id')
|
||||
good_count = ''
|
||||
submissionCloseDateTime = tender.get('submissionCloseDateTime')
|
||||
date_until = datetime.fromtimestamp(submissionCloseDateTime/1000).strftime('%Y-%m-%d')
|
||||
|
||||
params = {'id': tend_id,}
|
||||
response = requests.get('https://tenderplan.ru/api/tenders/get', params=params, cookies=cookies, headers=headers)
|
||||
if "Количество" in json.loads(response.json().get('json')).get("0").get("fv").get("0").get("fv").get("th").get("1").get("fv"):
|
||||
goods = json.loads(response.json().get('json')).get("0").get("fv").get("0").get("fv").get("tb")
|
||||
for good in goods:
|
||||
good_name = goods.get(good).get('0').get('fv')
|
||||
if res.get('url').get('art') in good_name:
|
||||
good_count = goods.get(good).get('1').get('fv')
|
||||
|
||||
print(f"name - {good_name}")
|
||||
|
||||
print(tend_id, date_until)
|
||||
|
||||
# поиск цены в таблицах
|
||||
price = []
|
||||
path = "tgbot/data/"
|
||||
abs_path = os.path.abspath(path)
|
||||
# Поиск и вывод файлов
|
||||
excel_files = [name for name in glob.glob(f'{abs_path}/price*.xls*')]
|
||||
# print(f'excel_files-{excel_files}')
|
||||
for file in excel_files:
|
||||
# print(f'file-{file}')
|
||||
# Загрузка Excel файла
|
||||
excel_file = file
|
||||
df = pd.read_excel(excel_file)
|
||||
search_term = res.get("url").get("art")
|
||||
if search_term.isalpha():
|
||||
continue
|
||||
# Поиск строк, содержащих текст запроса
|
||||
sear = df[df.apply(lambda row: row.astype(str).str.contains(search_term, case=False).any(), axis=1)].to_dict('index')
|
||||
print("sear ",sear)
|
||||
price.append({file.split('/')[-1]: sear})
|
||||
print("price ",price)
|
||||
|
||||
# price - ['Артикул','Бренд','Кол-во','Цена, руб. с НДС']
|
||||
|
||||
for pri in price:
|
||||
print("pri - ", sear)
|
||||
for keyprice, valprice in pri.items():
|
||||
for p in valprice.values():
|
||||
print("p - ", p)
|
||||
tenders_id.append({
|
||||
"article": res.get('url').get('article'),
|
||||
"art0": res.get('url').get('art'),
|
||||
# "price": price,
|
||||
"файл": keyprice,
|
||||
"Артикул": p.get("Артикул"),
|
||||
"Бренд": p.get("Бренд"),
|
||||
"Кол-во": p.get("Кол-во"),
|
||||
"Цена": p.get("Цена, руб. с НДС"),
|
||||
"good_count": good_count,
|
||||
"id_tender": tend_id,
|
||||
"url_tender": f"https://tenderplan.ru/app?tender={tend_id}",
|
||||
"date_until": date_until,
|
||||
"tend_name": tend_name,
|
||||
"platform": response.json().get('platform').get('name'),
|
||||
"href": response.json().get('href'),
|
||||
})
|
||||
else:
|
||||
tenders_id.append({
|
||||
"article": res.get('url').get('article'),
|
||||
"art0": res.get('url').get('art'),
|
||||
# "price": price,
|
||||
"файл": '--',
|
||||
"Артикул": '--',
|
||||
"Бренд": '--',
|
||||
"Кол-во": '--',
|
||||
"Цена": '--',
|
||||
"good_count": good_count,
|
||||
"id_tender": tend_id,
|
||||
"url_tender": f"https://tenderplan.ru/app?tender={tend_id}",
|
||||
"date_until": date_until,
|
||||
"tend_name": tend_name,
|
||||
"platform": response.json().get('platform').get('name'),
|
||||
"href": response.json().get('href'),
|
||||
})
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# pass
|
||||
return tenders_id
|
||||
|
||||
|
||||
async def search_in_tenderplan(urls = 0):
|
||||
tenders_id = []
|
||||
try:
|
||||
if urls == 0:
|
||||
urls = get_urls()
|
||||
else:
|
||||
return 0
|
||||
tasks = []
|
||||
# create instance of Semaphore
|
||||
sem = asyncio.Semaphore(3)
|
||||
retry_event = asyncio.Event()
|
||||
retry_event.set() # Устанавливаем событие в начальное состояние
|
||||
|
||||
results = []
|
||||
t = time.time()
|
||||
# Create client session that will ensure we dont open new connection
|
||||
# per each request.
|
||||
async with ClientSession(cookies=cookies, headers=headers) as session:
|
||||
tasks = [bound_fetch(sem, url, session, retry_event) for url in urls]
|
||||
|
||||
while True:
|
||||
results1 = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for result in results1:
|
||||
if isinstance(result, Exception):
|
||||
continue
|
||||
if result and result.get('response').get('status') == 429:
|
||||
retry_event.clear() # Останавливаем отправку запросов при 429
|
||||
await asyncio.sleep(5) # Ждем некоторое время перед повторной попыткой
|
||||
retry_event.set() # Разрешаем отправку запросов снова
|
||||
if all(result and result['response']['status'] == 200 for result in results if not isinstance(result, Exception)):
|
||||
break # Завершаем, если все запросы успешны
|
||||
|
||||
# responses = asyncio.gather(*tasks)
|
||||
results = results1
|
||||
print(time.time()-t)
|
||||
print(len(results))
|
||||
t1 = time.time()
|
||||
for res in results:
|
||||
try:
|
||||
if res.get('response').get('tenders'):
|
||||
tenders = res.get('response').get('tenders')
|
||||
#pagination
|
||||
# if len(tenders) > 50:
|
||||
# page = 1
|
||||
# urls2 = f"https://tenderplan.ru/api/tenders/getlist?page={page}&q={}"
|
||||
# while len(tenders) > 50:
|
||||
# response = requests.get(ur)
|
||||
# soup1 = BeautifulSoup(response.json, "html.parser")
|
||||
# tenders_id = sooup(soup1, tenders_id, res)
|
||||
########
|
||||
tenders_id = sooup(tenders_id, tenders, res)
|
||||
else:
|
||||
print('tenders none')
|
||||
# bot_logger.error(f"tenders none, скорее всего 429 ошибка")
|
||||
# raise Exception("tenders none")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"{e}")
|
||||
|
||||
print(time.time() - t1)
|
||||
for tend in tenders_id:
|
||||
print(tend)
|
||||
|
||||
get_excel_from_tenderplan(tenders_id)
|
||||
|
||||
return tenders_id
|
||||
except Exception as e:
|
||||
print(e)
|
||||
get_excel_from_tenderplan(tenders_id)
|
||||
bot_logger.error(f"{e}")
|
||||
|
||||
def get_excel_from_tenderplan(tenders_id, link = 'tgbot/data/tenders_tenderplan_from_art.xlsx'):
|
||||
# oldtends = pd.DataFrame(link)
|
||||
# newtends = pd.DataFrame(tenders_id)
|
||||
|
||||
# Предположим, ваш список с тендерами называется ``
|
||||
df = pd.DataFrame(tenders_id)
|
||||
df = df.drop_duplicates(ignore_index=True)
|
||||
|
||||
# Читаем существующий Excel файл, если он есть
|
||||
try:
|
||||
existing_df = pd.read_excel(link, engine='openpyxl')
|
||||
except FileNotFoundError:
|
||||
existing_df = pd.DataFrame()
|
||||
|
||||
# Объединяем DataFrame с помощью merge, используя indicator=True
|
||||
merged_df = df.merge(existing_df, how='left', indicator=True)
|
||||
|
||||
# Определяем строки, которые есть в ОБЕИХ таблицах
|
||||
in_both_df = merged_df[merged_df['_merge'] == 'both']
|
||||
|
||||
# Сохраняем в Excel с выделением нужных строк красным
|
||||
writer = pd.ExcelWriter(link, engine='xlsxwriter')
|
||||
df.to_excel(writer, sheet_name='Tenders', index=False)
|
||||
|
||||
# Получаем объект workbook
|
||||
workbook = writer.book
|
||||
# Получаем объект worksheet
|
||||
worksheet = workbook.get_worksheet_by_name('Tenders')
|
||||
|
||||
# Применяем стиль к строкам, которые есть в ОБЕИХ таблицах
|
||||
for index in in_both_df.index:
|
||||
worksheet.conditional_format(index+1, 0, index+1, len(df.columns)-1, {
|
||||
'type': 'no_errors',
|
||||
'format': workbook.add_format({'bg_color': '#FFC7CE'})
|
||||
})
|
||||
|
||||
# Сохраняем файл
|
||||
writer.close()
|
||||
|
||||
|
||||
|
||||
# newtends.to_excel(link)
|
||||
|
||||
|
||||
|
||||
######################################--------AUTOPITER-------########################################
|
||||
|
||||
|
||||
def split_search(search_string):
|
||||
search_split = []
|
||||
digit_count = sum(char.isdigit() for char in search_string)
|
||||
if digit_count <= 1:
|
||||
return []
|
||||
for word in search_string.split(" "):
|
||||
digit_count = sum(char.isdigit() for char in word)
|
||||
if (
|
||||
digit_count >= len(word) // 4
|
||||
and len(word)>=6
|
||||
and not(bool(re.search('[а-яА-Я]', word)))
|
||||
|
||||
):
|
||||
search_split.append(word)
|
||||
# if bool(re.search(r'\d', good_name)):
|
||||
# continue
|
||||
return search_split
|
||||
|
||||
|
||||
def search_in_autopiter(search: str):
|
||||
|
||||
|
||||
params = {
|
||||
'detailNumber': search,
|
||||
'isFullQuery': 'true',
|
||||
}
|
||||
|
||||
try:
|
||||
ap_search = search
|
||||
resp = requests.get('https://autopiter.ru/api/api/searchdetails', params=params)
|
||||
print("response - ", resp.status_code)
|
||||
print("good search - ", ap_search)
|
||||
goodauto = []
|
||||
|
||||
if resp.status_code == 200:
|
||||
params = {'idArticles': [position.get('id') for position in resp.json().get('data').get('catalogs')]}
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
print(get_cost_resp)
|
||||
|
||||
for position in resp.json().get('data').get('catalogs'):
|
||||
# print("position ",position)
|
||||
ap_name = position.get('name')
|
||||
ap_id = position.get('id')
|
||||
ap_number = position.get('number')
|
||||
if get_cost_resp.status_code == 200:
|
||||
ap_originalPrice = [cost.get('originalPrice') for cost in get_cost_resp.json().get('data') if cost.get('id') == ap_id and cost.get('originalPrice') > 0]
|
||||
if ap_originalPrice == []: continue
|
||||
else: ap_originalPrice = ap_originalPrice[0]
|
||||
else:
|
||||
ap_originalPrice = "--"
|
||||
# print("ap_originalPrice - ",ap_originalPrice)
|
||||
# print("descr совпадение - ",fuzz.partial_token_sort_ratio(name, descr))
|
||||
goodauto.append({
|
||||
'ap_search': ap_search,
|
||||
'ap_name': ILLEGAL_CHARACTERS_RE.sub(r'', ap_name),
|
||||
'fuzz': fuzz.partial_token_sort_ratio(ap_search, ap_name),
|
||||
'ap_number': ap_number,
|
||||
'ap_originalPrice': ap_originalPrice,
|
||||
'link_autopiter': f"https://autopiter.ru/goods/{ap_number}/{position.get('catalogUrl')}/id{ap_id}",
|
||||
'ap_id': ap_id,
|
||||
})
|
||||
elif resp.status_code == 429:
|
||||
raise Exception("429")
|
||||
if goodauto != []:
|
||||
goodauto = pd.DataFrame(goodauto).sort_values(by="fuzz",ascending=False)[:1]
|
||||
elif goodauto == []:
|
||||
goodauto = [{
|
||||
'ap_search': '----',
|
||||
'ap_name': '----',
|
||||
'fuzz': '',
|
||||
'ap_number': '',
|
||||
'ap_originalPrice': '',
|
||||
'link_autopiter': '----',
|
||||
}]
|
||||
return goodauto
|
||||
# goodsinauto.append(goodsinauto)
|
||||
|
||||
# print("name - ",search_in_autopiter(name))
|
||||
# amount = float(g.get("amount"))
|
||||
# if tend.get('article').split("/")[1] in name:
|
||||
# goods_name += name + " "
|
||||
# goods_amount += amount
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"{e}")
|
||||
return []
|
||||
|
||||
|
||||
def tenders_with_goods(pagecount: int = 1):
|
||||
try:
|
||||
tenders_with_goods = []
|
||||
count = 0
|
||||
page = 0
|
||||
countn = 50
|
||||
while True:
|
||||
try:
|
||||
params = {'page': page}
|
||||
response = requests.get('https://tenderplan.ru/api/tenders/getlist', params=params, cookies=cookies, headers=headers)
|
||||
# if (response.json().get('tenders') == []):
|
||||
# break
|
||||
if (page>=pagecount):
|
||||
break
|
||||
# if (count>countn):
|
||||
# break
|
||||
page += 1
|
||||
tenders = response.json().get('tenders')
|
||||
print(f"--------{len(tenders)}")
|
||||
for tend in tenders:
|
||||
try:
|
||||
tend_name = tend.get('orderName')
|
||||
id = tend.get('_id')
|
||||
print(f"tender -- {tend_name} -- {id}")
|
||||
params = {
|
||||
'id': id,
|
||||
}
|
||||
response = requests.get('https://tenderplan.ru/api/tenders/get', params=params, cookies=cookies, headers=headers)
|
||||
if "ObjectInfo" in response.json().get('json'):
|
||||
goods = json.loads(response.json().get('json'))["0"]["fv"]["0"]["fv"]["tb"]
|
||||
submission_close_timestamp = int(json.loads(response.json().get('json'))['1']['fv']['1']['fv'])
|
||||
print(submission_close_timestamp)
|
||||
submission_close_datetime = datetime.fromtimestamp(submission_close_timestamp/1000).strftime('%Y-%m-%d %H:%M:%S')
|
||||
for good in goods:
|
||||
# if (count>countn):
|
||||
# break
|
||||
good_name = goods.get(good).get('0').get('fv')
|
||||
print(f"name - {good_name}")
|
||||
# count += 1
|
||||
|
||||
for name in split_search(good_name):
|
||||
ap_search = search_in_autopiter(name)
|
||||
for ap_s in ap_search.iterrows():
|
||||
# print(ap_s)
|
||||
count += 1
|
||||
print("count",count)
|
||||
tenders_with_goods.append({
|
||||
"tend_name": tend_name,
|
||||
"tend_link": f"https://tenderplan.ru/app?tender={id}",
|
||||
"tend_under": submission_close_datetime,
|
||||
"good_name": good_name,
|
||||
"ap_good_name": name,
|
||||
"ap_search_name": ap_s[1].get('ap_name'),
|
||||
"ap_search_fuzz": ap_s[1].get('fuzz'),
|
||||
"ap_search_link": ap_s[1].get('link_autopiter'),
|
||||
"ap_id": ap_s[1].get('ap_id'),
|
||||
"ap_search_price": ap_s[1].get('ap_originalPrice'),
|
||||
})
|
||||
# break
|
||||
except Exception as e1:
|
||||
print("error -- ",e1)
|
||||
bot_logger.error(f"{e1}")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"{e}")
|
||||
print(f"count - {count}")
|
||||
|
||||
# for twg in tenders_with_goods:
|
||||
# print(twg)
|
||||
|
||||
print(f"excel!!!!!!!!!")
|
||||
tenders_with_goods = get_all_price(tenders_with_goods)
|
||||
tends = pd.DataFrame(tenders_with_goods)
|
||||
tends.to_excel(r'tgbot/data/tenders_with_goods.xlsx')
|
||||
print(f"excel!!!!!!!!!2")
|
||||
|
||||
|
||||
return tenders_with_goods
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"{e}")
|
||||
|
||||
|
||||
def get_all_price(tenders_with_goods):
|
||||
ap_ids = []
|
||||
c = 0
|
||||
tenders_with_goods2 = []
|
||||
for ap in tenders_with_goods:
|
||||
ap_ids.append(ap.get('ap_id'))
|
||||
|
||||
params = {'idArticles': ap_ids}
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
while get_cost_resp.status_code == 429:
|
||||
time.sleep(5)
|
||||
c += 1
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
if c>10: break
|
||||
|
||||
print(get_cost_resp.status_code)
|
||||
cost_resp = get_cost_resp.json().get('data')
|
||||
for ap in tenders_with_goods:
|
||||
try:
|
||||
id = ap.get("ap_id")
|
||||
cost = [cost.get('originalPrice') for cost in cost_resp if cost.get('id') == id]
|
||||
|
||||
if cost:
|
||||
ap['ap_search_price'] = cost[0]
|
||||
tenders_with_goods2.append(ap)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
bot_logger.error(f"{e}")
|
||||
return tenders_with_goods2
|
||||
|
||||
|
||||
def beautitext(text: str):
|
||||
|
||||
# texts = re.findall(r'\w', text)
|
||||
|
||||
tt = []
|
||||
|
||||
texts = text.split(" ")
|
||||
for te in texts:
|
||||
digit_count = sum(char.isdigit() for char in te)
|
||||
if digit_count >= len(te) // 2:
|
||||
tt.append(te)
|
||||
# if bool(re.search(r'\d', te)):
|
||||
# tt.append(te)
|
||||
|
||||
|
||||
return str(tt)
|
||||
|
||||
# print(beautitext('Помпа ЗМЗ-406, 409 /Евро-3, Евро-4'))
|
||||
# tenders_with_goods()
|
||||
|
||||
|
||||
|
||||
# print(search_in_autopiter('Форсунка топливная Common Rail КАМАЗ'))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# curl 'https://tenderplan.ru/api/tenders/getlist?' \
|
||||
# -X 'GET' \
|
||||
# -H 'Accept: */*' \
|
||||
# -H 'Authorization: Bearer f7dcac67acdb2a348f5c81fc26cfafaba892a7bc02dceb97ffe079ad60b0edb4399522590c067b24459b785fa019006943700cc47033ca26b7aecd22f3777077' \
|
||||
# -H 'Sec-Fetch-Site: same-origin' \
|
||||
# -H 'Accept-Language: ru' \
|
||||
# -H 'Accept-Encoding: gzip, deflate, br' \
|
||||
# -H 'Sec-Fetch-Mode: cors' \
|
||||
# -H 'Host: tenderplan.ru' \
|
||||
# -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15' \
|
||||
# -H 'Referer: https://tenderplan.ru/app?key=0&tender=6628ae9952e24fc13583dd05' \
|
||||
# -H 'Connection: keep-alive' \
|
||||
# -H 'Cookie: jwt=s%3ABearer%20f7dcac67acdb2a348f5c81fc26cfafaba892a7bc02dceb97ffe079ad60b0edb4399522590c067b24459b785fa019006943700cc47033ca26b7aecd22f3777077.UH%2BcCzOylTzLr%2BF6Hf4kerem6GuMoVK%2FBSiOYmPCkEc; source=key=0&tender=6628ae9952e24fc13583dd05; previousUrl=tenderplan.ru%2Fbitrix24%2Finstructions%2F; tildasid=1713941269630.966260; tildauid=1713888711831.359844; referer=https://tenderplan.ru/app; __ddg1_=ZKa7JlUseYuy3cvawO9W' \
|
||||
# -H 'Sec-Fetch-Dest: empty' \
|
||||
# -H 'Socket: ZTvzcuitnjsM-m4OBCP4'
|
||||
163
tgbot/services/test.py
Normal file
163
tgbot/services/test.py
Normal file
@ -0,0 +1,163 @@
|
||||
import requests
|
||||
import json
|
||||
import pandas as pd
|
||||
import re
|
||||
from thefuzz import fuzz, process
|
||||
from bs4 import BeautifulSoup
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
cookies = {
|
||||
'jwt': 's%3ABearer%20...',
|
||||
'referer': 'https://tenderplan.ru/app?key=0&tender=6639e01152e24fc13574139f',
|
||||
'source': 'key=0&tender=6639e01152e24fc13574139f',
|
||||
'tildauid': '1713888711831.359844',
|
||||
'__ddg1_': 'ZKa7JlUseYuy3cvawO9W',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Accept': '*/*',
|
||||
'Authorization': 'Bearer a4dc57cc44d5ca62a06a7b19660840a66f3048028b417bbc813a1acf6f3691da841b9120373431377409359f64430b0644ee22ddf072fbe6ad656b57eeebe83d',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'Accept-Language': 'ru',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Host': 'tenderplan.ru',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://tenderplan.ru/app?key=0&tender=6639e01152e24fc13574139f',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
}
|
||||
|
||||
def clean_text(text):
|
||||
return re.sub(r'[^\w\s]', '', text).lower()
|
||||
|
||||
def split_search(search_string):
|
||||
search_split = []
|
||||
digit_count = sum(char.isdigit() for char in search_string)
|
||||
if digit_count <= 1:
|
||||
return []
|
||||
for word in search_string.split(" "):
|
||||
digit_count = sum(char.isdigit() for char in word)
|
||||
if digit_count >= len(word) // 4 and len(word) >= 6 and not re.search('[а-яА-Я]', word):
|
||||
search_split.append(word)
|
||||
return search_split
|
||||
|
||||
def search_in_autopiter(search):
|
||||
params = {'detailNumber': search, 'isFullQuery': 'true'}
|
||||
try:
|
||||
resp = requests.get('https://autopiter.ru/api/api/searchdetails', params=params)
|
||||
goodauto = []
|
||||
|
||||
if resp.status_code == 200:
|
||||
params = {'idArticles': [position.get('id') for position in resp.json().get('data').get('catalogs')]}
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
|
||||
for position in resp.json().get('data').get('catalogs'):
|
||||
ap_name = position.get('name')
|
||||
ap_id = position.get('id')
|
||||
ap_number = position.get('number')
|
||||
if get_cost_resp.status_code == 200:
|
||||
ap_originalPrice = next((cost.get('originalPrice') for cost in get_cost_resp.json().get('data') if cost.get('id') == ap_id and cost.get('originalPrice') > 0), "--")
|
||||
else:
|
||||
ap_originalPrice = "--"
|
||||
goodauto.append({
|
||||
'ap_search': search,
|
||||
'ap_name': ILLEGAL_CHARACTERS_RE.sub(r'', ap_name),
|
||||
'fuzz': fuzz.partial_token_sort_ratio(search, ap_name),
|
||||
'ap_number': ap_number,
|
||||
'ap_originalPrice': ap_originalPrice,
|
||||
'link_autopiter': f"https://autopiter.ru/goods/{ap_number}/{position.get('catalogUrl')}/id{ap_id}",
|
||||
'ap_id': ap_id,
|
||||
})
|
||||
elif resp.status_code == 429:
|
||||
raise Exception("429")
|
||||
if goodauto:
|
||||
goodauto = pd.DataFrame(goodauto).sort_values(by="fuzz", ascending=False)[:1]
|
||||
else:
|
||||
goodauto = [{
|
||||
'ap_search': '----',
|
||||
'ap_name': '----',
|
||||
'fuzz': '',
|
||||
'ap_number': '',
|
||||
'ap_originalPrice': '',
|
||||
'link_autopiter': '----',
|
||||
}]
|
||||
return goodauto
|
||||
except Exception as e:
|
||||
bot_logger.error(f"{e}")
|
||||
return []
|
||||
|
||||
def tenders_with_goods(pagecount=1):
|
||||
try:
|
||||
tenders_with_goods = []
|
||||
page = 0
|
||||
|
||||
while page < pagecount:
|
||||
params = {'page': page}
|
||||
response = requests.get('https://tenderplan.ru/api/tenders/getlist', params=params, cookies=cookies, headers=headers)
|
||||
tenders = response.json().get('tenders')
|
||||
page += 1
|
||||
|
||||
for tend in tenders:
|
||||
tend_name = tend.get('orderName')
|
||||
id = tend.get('_id')
|
||||
params = {'id': id}
|
||||
response = requests.get('https://tenderplan.ru/api/tenders/get', params=params, cookies=cookies, headers=headers)
|
||||
if "ObjectInfo" in response.json().get('json'):
|
||||
goods = json.loads(response.json().get('json'))["0"]["fv"]["0"]["fv"]["tb"]
|
||||
submission_close_timestamp = int(json.loads(response.json().get('json'))['1']['fv']['1']['fv'])
|
||||
submission_close_datetime = datetime.fromtimestamp(submission_close_timestamp / 1000).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
for good in goods.values():
|
||||
good_name = good.get('0').get('fv')
|
||||
for name in split_search(good_name):
|
||||
ap_search = search_in_autopiter(name)
|
||||
for ap_s in ap_search.iterrows():
|
||||
tenders_with_goods.append({
|
||||
"tend_name": tend_name,
|
||||
"tend_link": f"https://tenderplan.ru/app?tender={id}",
|
||||
"tend_under": submission_close_datetime,
|
||||
"good_name": good_name,
|
||||
"ap_good_name": name,
|
||||
"ap_search_name": ap_s[1].get('ap_name'),
|
||||
"ap_search_fuzz": ap_s[1].get('fuzz'),
|
||||
"ap_search_link": ap_s[1].get('link_autopiter'),
|
||||
"ap_id": ap_s[1].get('ap_id'),
|
||||
"ap_search_price": ap_s[1].get('ap_originalPrice'),
|
||||
})
|
||||
tenders_with_goods = get_all_price(tenders_with_goods)
|
||||
tends = pd.DataFrame(tenders_with_goods)
|
||||
tends.to_excel('tgbot/data/tenders_with_goods.xlsx')
|
||||
|
||||
return tenders_with_goods
|
||||
except Exception as e:
|
||||
bot_logger.error(f"{e}")
|
||||
|
||||
def get_all_price(tenders_with_goods):
|
||||
ap_ids = [ap.get('ap_id') for ap in tenders_with_goods]
|
||||
params = {'idArticles': ap_ids}
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
|
||||
retry_count = 0
|
||||
while get_cost_resp.status_code == 429 and retry_count < 10:
|
||||
time.sleep(5)
|
||||
retry_count += 1
|
||||
get_cost_resp = requests.get('https://autopiter.ru/api/api/appraise/getcosts', params=params)
|
||||
|
||||
if get_cost_resp.status_code == 200:
|
||||
cost_resp = get_cost_resp.json().get('data')
|
||||
for ap in tenders_with_goods:
|
||||
id = ap.get("ap_id")
|
||||
cost = next((cost.get('originalPrice') for cost in cost_resp if cost.get('id') == id), None)
|
||||
if cost:
|
||||
ap['ap_search_price'] = cost
|
||||
|
||||
return tenders_with_goods
|
||||
|
||||
def beautitext(text):
|
||||
texts = text.split(" ")
|
||||
tt = [te for te in texts if sum(char.isdigit() for char in te) >= len(te) // 2]
|
||||
return str(tt)
|
||||
BIN
tgbot/utils/.DS_Store
vendored
Normal file
BIN
tgbot/utils/.DS_Store
vendored
Normal file
Binary file not shown.
0
tgbot/utils/__init__.py
Normal file
0
tgbot/utils/__init__.py
Normal file
387
tgbot/utils/const_functions.py
Normal file
387
tgbot/utils/const_functions.py
Normal file
@ -0,0 +1,387 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
import pytz
|
||||
from aiogram import Bot
|
||||
from aiogram.types import InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup, \
|
||||
ReplyKeyboardMarkup
|
||||
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.data.config import get_admins, BOT_TIMEZONE
|
||||
|
||||
######################################## AIOGRAM ########################################
|
||||
# Генерация реплай кнопки
|
||||
def rkb(text: str) -> KeyboardButton:
|
||||
return KeyboardButton(text=text)
|
||||
|
||||
|
||||
# Генерация инлайн кнопки
|
||||
def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: str = None) -> InlineKeyboardButton:
|
||||
if data is not None:
|
||||
return InlineKeyboardButton(text=text, callback_data=data)
|
||||
elif url is not None:
|
||||
return InlineKeyboardButton(text=text, url=url)
|
||||
elif switch is not None:
|
||||
return InlineKeyboardButton(text=text, switch_inline_query=switch)
|
||||
elif web is not None:
|
||||
return InlineKeyboardButton(text=text, web_app=WebAppInfo(url=web))
|
||||
|
||||
|
||||
# Удаление сообщения с обработкой ошибки от телеграма
|
||||
async def del_message(message: Message):
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
# Умная отправка сообщений (автоотправка сообщения с фото или без)
|
||||
async def smart_message(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup] = None,
|
||||
photo: Union[str, None] = None,
|
||||
):
|
||||
if photo is not None and photo.title() != "None":
|
||||
await bot.send_photo(
|
||||
chat_id=user_id,
|
||||
photo=photo,
|
||||
caption=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
|
||||
# Отправка сообщения всем админам
|
||||
async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
|
||||
for admin in get_admins():
|
||||
try:
|
||||
if str(admin) != str(not_me):
|
||||
await bot.send_message(
|
||||
admin,
|
||||
text,
|
||||
reply_markup=markup,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except:
|
||||
...
|
||||
|
||||
# # Отправка сообщения пользователям по тендорам
|
||||
# async def send_employees(bot: Bot, text: str, markup=None, not_me=0):
|
||||
# get_users = get_employees()
|
||||
# bot_logger.warning("employee {get_users}")
|
||||
|
||||
|
||||
# # for admin in get_admins():
|
||||
# # try:
|
||||
# # if str(admin) != str(not_me):
|
||||
# # await bot.send_message(
|
||||
# # admin,
|
||||
# # text,
|
||||
# # reply_markup=markup,
|
||||
# # disable_web_page_preview=True,
|
||||
# # )
|
||||
# # except:
|
||||
# # ...
|
||||
|
||||
######################################## ПРОЧЕЕ ########################################
|
||||
# Удаление отступов в многострочной строке ("""text""")
|
||||
def ded(get_text: str) -> str:
|
||||
if get_text is not None:
|
||||
split_text = get_text.split("\n")
|
||||
if split_text[0] == "": split_text.pop(0)
|
||||
if split_text[-1] == "": split_text.pop()
|
||||
save_text = []
|
||||
|
||||
for text in split_text:
|
||||
while text.startswith(" "):
|
||||
text = text[1:].strip()
|
||||
|
||||
save_text.append(text)
|
||||
get_text = "\n".join(save_text)
|
||||
else:
|
||||
get_text = ""
|
||||
|
||||
return get_text
|
||||
|
||||
|
||||
# Очистка текста от HTML тэгов ('<b>test</b>' -> *b*test*/b*)
|
||||
def clear_html(get_text: str) -> str:
|
||||
if get_text is not None:
|
||||
if "</" in get_text: get_text = get_text.replace("<", "*")
|
||||
if "<" in get_text: get_text = get_text.replace("<", "*")
|
||||
if ">" in get_text: get_text = get_text.replace(">", "*")
|
||||
else:
|
||||
get_text = ""
|
||||
|
||||
return get_text
|
||||
|
||||
|
||||
# Очистка пробелов в списке (['', 1, ' ', 2] -> [1, 2])
|
||||
def clear_list(get_list: list) -> list:
|
||||
while "" in get_list: get_list.remove("")
|
||||
while " " in get_list: get_list.remove(" ")
|
||||
while "." in get_list: get_list.remove(".")
|
||||
while "," in get_list: get_list.remove(",")
|
||||
while "\r" in get_list: get_list.remove("\r")
|
||||
while "\n" in get_list: get_list.remove("\n")
|
||||
|
||||
return get_list
|
||||
|
||||
|
||||
# Разбив списка на несколько частей ([1, 2, 3, 4] 2 -> [[1, 2], [3, 4]])
|
||||
def split_list(get_list: list, count: int) -> list[list]:
|
||||
return [get_list[i:i + count] for i in range(0, len(get_list), count)]
|
||||
|
||||
|
||||
# Получение текущей даты (True - дата с временем, False - дата без времени)
|
||||
def get_date(full: bool = True) -> str:
|
||||
if full:
|
||||
return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S")
|
||||
else:
|
||||
return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Получение текущего unix времени (True - время в наносекундах, False - время в секундах)
|
||||
def get_unix(full: bool = False) -> int:
|
||||
if full:
|
||||
return time.time_ns()
|
||||
else:
|
||||
return int(time.time())
|
||||
|
||||
|
||||
# Конвертация unix в дату и даты в unix
|
||||
def convert_date(from_time, full=True, second=True) -> Union[str, int]:
|
||||
from tgbot.data.config import BOT_TIMEZONE
|
||||
|
||||
if "-" in str(from_time):
|
||||
from_time = from_time.replace("-", ".")
|
||||
|
||||
if str(from_time).isdigit():
|
||||
if full:
|
||||
to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S")
|
||||
elif second:
|
||||
to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M")
|
||||
else:
|
||||
to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y")
|
||||
else:
|
||||
if " " in str(from_time):
|
||||
cache_time = from_time.split(" ")
|
||||
|
||||
if ":" in cache_time[0]:
|
||||
cache_date = cache_time[1].split(".")
|
||||
cache_time = cache_time[0].split(":")
|
||||
else:
|
||||
cache_date = cache_time[0].split(".")
|
||||
cache_time = cache_time[1].split(":")
|
||||
|
||||
if len(cache_date[0]) == 4:
|
||||
x_year, x_month, x_day = cache_date[0], cache_date[1], cache_date[2]
|
||||
else:
|
||||
x_year, x_month, x_day = cache_date[2], cache_date[1], cache_date[0]
|
||||
|
||||
x_hour, x_minute, x_second = cache_time[0], cache_time[2], cache_time[2]
|
||||
|
||||
from_time = f"{x_day}.{x_month}.{x_year} {x_hour}:{x_minute}:{x_second}"
|
||||
else:
|
||||
cache_date = from_time.split(".")
|
||||
|
||||
if len(cache_date[0]) == 4:
|
||||
x_year, x_month, x_day = cache_date[0], cache_date[1], cache_date[2]
|
||||
else:
|
||||
x_year, x_month, x_day = cache_date[2], cache_date[1], cache_date[0]
|
||||
|
||||
from_time = f"{x_day}.{x_month}.{x_year}"
|
||||
|
||||
if " " in str(from_time):
|
||||
to_time = int(datetime.strptime(from_time, "%d.%m.%Y %H:%M:%S").timestamp())
|
||||
else:
|
||||
to_time = int(datetime.strptime(from_time, "%d.%m.%Y").timestamp())
|
||||
|
||||
return to_time
|
||||
|
||||
|
||||
# Генерация уникального айди
|
||||
def gen_id() -> int:
|
||||
mac_address = uuid.getnode()
|
||||
time_unix = int(str(time.time_ns())[:16])
|
||||
|
||||
return mac_address + time_unix
|
||||
|
||||
|
||||
# Генерация пароля | default, number, letter, onechar
|
||||
def gen_password(len_password: int = 16, type_password: str = "default") -> str:
|
||||
if type_password == "default":
|
||||
char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
|
||||
elif type_password == "letter":
|
||||
char_password = list("abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
|
||||
elif type_password == "number":
|
||||
char_password = list("1234567890")
|
||||
elif type_password == "onechar":
|
||||
char_password = list("1234567890")
|
||||
|
||||
random.shuffle(char_password)
|
||||
random_chars = "".join([random.choice(char_password) for x in range(len_password)])
|
||||
|
||||
if type_password == "onechar":
|
||||
random_chars = f"{random.choice('abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ')}{random_chars[1:]}"
|
||||
|
||||
return random_chars
|
||||
|
||||
|
||||
# Дополнение к числу корректного времени (1 -> 1 день, 3 -> 3 дня)
|
||||
def convert_times(get_time: int, get_type: str = "day") -> str:
|
||||
get_time = int(get_time)
|
||||
if get_time < 0: get_time = 0
|
||||
|
||||
if get_type == "second":
|
||||
get_list = ['секунда', 'секунды', 'секунд']
|
||||
elif get_type == "minute":
|
||||
get_list = ['минута', 'минуты', 'минут']
|
||||
elif get_type == "hour":
|
||||
get_list = ['час', 'часа', 'часов']
|
||||
elif get_type == "day":
|
||||
get_list = ['день', 'дня', 'дней']
|
||||
elif get_type == "month":
|
||||
get_list = ['месяц', 'месяца', 'месяцев']
|
||||
else:
|
||||
get_list = ['год', 'года', 'лет']
|
||||
|
||||
if get_time % 10 == 1 and get_time % 100 != 11:
|
||||
count = 0
|
||||
elif 2 <= get_time % 10 <= 4 and (get_time % 100 < 10 or get_time % 100 >= 20):
|
||||
count = 1
|
||||
else:
|
||||
count = 2
|
||||
|
||||
return f"{get_time} {get_list[count]}"
|
||||
|
||||
|
||||
# Проверка на булевый тип
|
||||
def is_bool(value: Union[bool, str, int]) -> bool:
|
||||
value = str(value).lower()
|
||||
|
||||
if value in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
return True
|
||||
elif value in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return False
|
||||
else:
|
||||
raise ValueError(f"invalid truth value {value}")
|
||||
|
||||
|
||||
######################################## ЧИСЛА ########################################
|
||||
# Преобразование экспоненциальных чисел в читаемый вид (1e-06 -> 0.000001)
|
||||
def snum(amount: Union[int, float], remains: int = 2) -> str:
|
||||
format_str = "{:." + str(remains) + "f}"
|
||||
str_amount = format_str.format(float(amount))
|
||||
|
||||
if remains != 0:
|
||||
if "." in str_amount:
|
||||
remains_find = str_amount.find(".")
|
||||
remains_save = remains_find + 8 - (8 - remains) + 1
|
||||
|
||||
str_amount = str_amount[:remains_save]
|
||||
|
||||
if "." in str(str_amount):
|
||||
while str(str_amount).endswith('0'): str_amount = str(str_amount)[:-1]
|
||||
|
||||
if str(str_amount).endswith('.'): str_amount = str(str_amount)[:-1]
|
||||
|
||||
return str(str_amount)
|
||||
|
||||
|
||||
# Конвертация любого числа в вещественное, с удалением нулей в конце (remains - округление)
|
||||
def to_float(get_number, remains: int = 2) -> Union[int, float]:
|
||||
if "," in str(get_number):
|
||||
get_number = str(get_number).replace(",", ".")
|
||||
|
||||
if "." in str(get_number):
|
||||
get_last = str(get_number).split(".")
|
||||
|
||||
if str(get_last[1]).endswith("0"):
|
||||
while True:
|
||||
if str(get_number).endswith("0"):
|
||||
get_number = str(get_number)[:-1]
|
||||
else:
|
||||
break
|
||||
|
||||
get_number = round(float(get_number), remains)
|
||||
|
||||
str_number = snum(get_number)
|
||||
if "." in str_number:
|
||||
if str_number.split(".")[1] == "0":
|
||||
get_number = int(get_number)
|
||||
else:
|
||||
get_number = float(get_number)
|
||||
else:
|
||||
get_number = int(get_number)
|
||||
|
||||
return get_number
|
||||
|
||||
|
||||
# Конвертация вещественного числа в целочисленное
|
||||
def to_int(get_number: float) -> int:
|
||||
if "," in get_number:
|
||||
get_number = str(get_number).replace(",", ".")
|
||||
|
||||
get_number = int(round(float(get_number)))
|
||||
|
||||
return get_number
|
||||
|
||||
|
||||
# Проверка ввода на число
|
||||
def is_number(get_number: Union[str, int, float]) -> bool:
|
||||
if str(get_number).isdigit():
|
||||
return True
|
||||
else:
|
||||
if "," in str(get_number): get_number = str(get_number).replace(",", ".")
|
||||
|
||||
try:
|
||||
float(get_number)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
# Преобразование числа в читаемый вид (123456789 -> 123 456 789)
|
||||
def format_rate(amount: Union[float, int], around: int = 2) -> str:
|
||||
if "," in str(amount): amount = float(str(amount).replace(",", "."))
|
||||
if " " in str(amount): amount = float(str(amount).replace(" ", ""))
|
||||
amount = str(round(amount, around))
|
||||
|
||||
out_amount, save_remains = [], ""
|
||||
|
||||
if "." in amount: save_remains = amount.split(".")[1]
|
||||
save_amount = [char for char in str(int(float(amount)))]
|
||||
|
||||
if len(save_amount) % 3 != 0:
|
||||
if (len(save_amount) - 1) % 3 == 0:
|
||||
out_amount.extend([save_amount[0]])
|
||||
save_amount.pop(0)
|
||||
elif (len(save_amount) - 2) % 3 == 0:
|
||||
out_amount.extend([save_amount[0], save_amount[1]])
|
||||
save_amount.pop(1)
|
||||
save_amount.pop(0)
|
||||
else:
|
||||
print("Error 4388326")
|
||||
|
||||
for x, char in enumerate(save_amount):
|
||||
if x % 3 == 0: out_amount.append(" ")
|
||||
out_amount.append(char)
|
||||
|
||||
response = "".join(out_amount).strip() + "." + save_remains
|
||||
|
||||
if response.endswith("."):
|
||||
response = response[:-1]
|
||||
|
||||
return response
|
||||
0
tgbot/utils/misc/__init__.py
Normal file
0
tgbot/utils/misc/__init__.py
Normal file
49
tgbot/utils/misc/bot_commands.py
Normal file
49
tgbot/utils/misc/bot_commands.py
Normal file
@ -0,0 +1,49 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Bot
|
||||
from aiogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeDefault
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
|
||||
# Команды для юзеров
|
||||
user_commands = [
|
||||
BotCommand(command="start", description="Restart bot"),
|
||||
BotCommand(command="parser", description="Запускает поиск тендоров"),
|
||||
BotCommand(command="status", description="статус бота"),
|
||||
BotCommand(command="get_notif", description="получать уведомления"),
|
||||
BotCommand(command="stop_get", description="остановить получение уведомлений"),
|
||||
BotCommand(command="get_sheet", description="Получить таблицу"),
|
||||
BotCommand(command="excel_from_tenders", description="Поиск за все время"),
|
||||
BotCommand(command="tenders_with_goods", description="Поиск в автопитере"),
|
||||
BotCommand(command="tenderplan", description="Поиск в tenderplan"),
|
||||
BotCommand(command="log", description="Get Logs"),
|
||||
# BotCommand(command="inline", description="🌀 Get Inline keyboard"),
|
||||
]
|
||||
|
||||
# Команды для админов
|
||||
admin_commands = [
|
||||
BotCommand(command="start", description="Restart bot"),
|
||||
BotCommand(command="parser", description="Запускает поиск тендоров"),
|
||||
BotCommand(command="status", description="статус бота"),
|
||||
BotCommand(command="get_notif", description="получать уведомления"),
|
||||
BotCommand(command="stop_get_notif", description="остановить получение уведомлений"),
|
||||
BotCommand(command="start_shed", description="Запуск работы по расписанию"),
|
||||
BotCommand(command="stop_shed", description="остановка работы по расписанию"),
|
||||
BotCommand(command="get_sheet", description="Получить таблицу"),
|
||||
BotCommand(command="excel_from_tenders", description="Поиск за все время"),
|
||||
BotCommand(command="tenders_with_goods", description="Поиск в автопитере"),
|
||||
BotCommand(command="tenderplan", description="Поиск в tenderplan"),
|
||||
BotCommand(command="log", description="Get Logs"),
|
||||
# BotCommand(command="inline", description="🌀 Get Inline keyboard"),
|
||||
BotCommand(command="db", description="📦 Get Database"),
|
||||
]
|
||||
|
||||
|
||||
# Установка команд
|
||||
async def set_commands(bot: Bot):
|
||||
await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
|
||||
|
||||
for admin in get_admins():
|
||||
try:
|
||||
await bot.set_my_commands(admin_commands, scope=BotCommandScopeChat(chat_id=admin))
|
||||
except:
|
||||
pass
|
||||
22
tgbot/utils/misc/bot_filters.py
Normal file
22
tgbot/utils/misc/bot_filters.py
Normal file
@ -0,0 +1,22 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
from tgbot.database.db_users import Userx
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
|
||||
# Проверка на админа
|
||||
class IsAdmin(BaseFilter):
|
||||
async def __call__(self, message: Message) -> bool:
|
||||
if message.from_user.id in get_admins():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# Отправка сообщения пользователям по тендорам
|
||||
def get_employees():
|
||||
get_users = Userx.gets(notif = "True")
|
||||
# bot_logger.warning(f"employee {get_users}")
|
||||
return get_users
|
||||
32
tgbot/utils/misc/bot_logging.py
Normal file
32
tgbot/utils/misc/bot_logging.py
Normal file
@ -0,0 +1,32 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import logging as bot_logger
|
||||
|
||||
import colorlog
|
||||
|
||||
from tgbot.data.config import PATH_LOGS
|
||||
|
||||
# Формат логгирования
|
||||
log_formatter_file = bot_logger.Formatter("%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s")
|
||||
log_formatter_console = colorlog.ColoredFormatter(
|
||||
"%(purple)s%(levelname)s %(blue)s|%(purple)s %(asctime)s %(blue)s|%(purple)s %(filename)s:%(lineno)d %(blue)s|%(purple)s %(message)s%(red)s",
|
||||
datefmt="%d-%m-%Y %H:%M:%S",
|
||||
)
|
||||
|
||||
# Логгирование в файл logs.log
|
||||
file_handler = bot_logger.FileHandler(PATH_LOGS, "w", "utf-8")
|
||||
file_handler.setFormatter(log_formatter_file)
|
||||
file_handler.setLevel(bot_logger.DEBUG)
|
||||
|
||||
# Логгирование в консоль
|
||||
console_handler = bot_logger.StreamHandler()
|
||||
console_handler.setFormatter(log_formatter_console)
|
||||
console_handler.setLevel(bot_logger.DEBUG)
|
||||
|
||||
# Подключение настроек логгирования
|
||||
bot_logger.basicConfig(
|
||||
format="%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s",
|
||||
handlers=[
|
||||
file_handler,
|
||||
console_handler
|
||||
]
|
||||
)
|
||||
7
tgbot/utils/misc/bot_models.py
Normal file
7
tgbot/utils/misc/bot_models.py
Normal file
@ -0,0 +1,7 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from tgbot.services.api_session import AsyncRequestSession
|
||||
|
||||
FSM = FSMContext
|
||||
ARS = AsyncRequestSession
|
||||
123
tgbot/utils/misc_functions.py
Normal file
123
tgbot/utils/misc_functions.py
Normal file
@ -0,0 +1,123 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Bot
|
||||
from aiogram.types import FSInputFile, BufferedInputFile
|
||||
|
||||
from tgbot.data.config import get_admins, PATH_DATABASE, start_status
|
||||
from tgbot.utils.const_functions import get_date, send_admins
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.services.parser_tendors import get_tenders_from_url, get_excel_from_tenders
|
||||
from tgbot.utils.misc.bot_filters import get_employees
|
||||
from tgbot.services.tender_plan import tenders_with_goods, search_in_tenderplan
|
||||
import os
|
||||
import io
|
||||
import pandas as pd
|
||||
|
||||
# Отправка сообщения пользователям по тендорам
|
||||
# async def get_employees(bot: Bot, text: str, markup=None, not_me=0):
|
||||
# get_users = Userx.gets(notif = "False")
|
||||
# bot_logger.warning("employee {get_users}")
|
||||
|
||||
# Отправка сообщения пользователям по тендорам
|
||||
async def send_employees(bot: Bot, text: str, markup=None, not_me=0):
|
||||
get_empl = get_employees()
|
||||
bot_logger.warning(f"employee1 {get_empl}")
|
||||
for empl in get_empl:
|
||||
await bot.send_message(
|
||||
empl.user_id,
|
||||
text,
|
||||
reply_markup=markup,
|
||||
# disable_web_page_preview=True,
|
||||
disable_notification=True,
|
||||
)
|
||||
|
||||
# for admin in get_admins():
|
||||
# try:
|
||||
# if str(admin) != str(not_me):
|
||||
# await bot.send_message(
|
||||
# admin,
|
||||
# text,
|
||||
# reply_markup=markup,
|
||||
# disable_web_page_preview=True,
|
||||
# )
|
||||
# except:
|
||||
# ...
|
||||
|
||||
|
||||
# Выполнение функции после запуска бота (рассылка админам о запуске бота)
|
||||
async def startup_notify(bot: Bot):
|
||||
if len(get_admins()) >= 1 and start_status:
|
||||
await send_admins(bot, "<b>✅ Bot was started</b>")
|
||||
|
||||
|
||||
# Автоматические бэкапы БД
|
||||
async def autobackup_admin(bot: Bot):
|
||||
for admin in get_admins():
|
||||
try:
|
||||
await bot.send_document(
|
||||
admin,
|
||||
FSInputFile(PATH_DATABASE),
|
||||
caption=f"<b>📦 #AUTOBACKUP | <code>{get_date()}</code></b>",
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# поиск тендора по расписанию
|
||||
async def tenders_sched(bot: Bot):
|
||||
try:
|
||||
tenders_id = await get_tenders_from_url()
|
||||
bot_logger.warning(f"tenders_id: {tenders_id}")
|
||||
if (len(str(tenders_id))>4000):
|
||||
tenders_id = pd.DataFrame(tenders_id)
|
||||
get_excel_from_tenders(tenders_id)
|
||||
with io.BytesIO() as output:
|
||||
tenders_id.to_excel(output)
|
||||
excel_data = output.getvalue()
|
||||
file_excel = io.BytesIO(excel_data)
|
||||
get_empl = get_employees()
|
||||
bot_logger.warning(f"employee1 {get_empl}")
|
||||
for empl in get_empl:
|
||||
await bot.send_document(
|
||||
empl.user_id,
|
||||
BufferedInputFile(file_excel.getvalue(), f"everyday_articles.xlsx"),
|
||||
caption = f"Нашлось по расписанию",
|
||||
disable_notification=True)
|
||||
else:
|
||||
answ = ""
|
||||
for num, tend in enumerate(tenders_id):
|
||||
answ += f"{num+1}. Наименование/артикул: {tend['article']}, id тендера: {tend['id_tender']}, прием до: {tend['date_until']}, url: {tend['url_tender']} \n \n"
|
||||
mes = f"Автоматический поиск тендоров: \n \n"
|
||||
if answ == "":
|
||||
mes += "Ничего не найдено"
|
||||
else:
|
||||
mes += answ
|
||||
await send_employees(bot, mes)
|
||||
except:
|
||||
send_admins(bot, f"Ошибка при автоматическом поиске")
|
||||
|
||||
|
||||
# поиск тендора в автопитере по расписанию
|
||||
async def tenders_sched_ap(bot: Bot):
|
||||
bot_logger.warning(f"tenders_sched_ap start")
|
||||
try:
|
||||
tenders_with_goods(20)
|
||||
await send_employees(bot, "поиск по автопитеру выполнен")
|
||||
except:
|
||||
send_admins(bot, f"Ошибка при поиск по автопитеру")
|
||||
|
||||
|
||||
# Поиск тендеров в tenderplan по расписанию
|
||||
async def tenders_sched_ap_in_tenderplan(bot: Bot):
|
||||
bot_logger.warning(f"tenders_sched_ap_in_tenderplan")
|
||||
await search_in_tenderplan()
|
||||
get_empl = get_employees()
|
||||
for empl in get_empl:
|
||||
await bot.send_document(
|
||||
empl.user_id,
|
||||
FSInputFile('tgbot/data/tenders_tenderplan_from_art.xlsx'),
|
||||
caption = f"Нашлось из tenderplan по расписанию",
|
||||
disable_notification=True)
|
||||
# await message.answer_document(
|
||||
# FSInputFile('tgbot/data/tenders_tenderplan_from_art.xlsx'),
|
||||
# caption=f"Тендеры из tenderplan.",
|
||||
# )
|
||||
Loading…
Reference in New Issue
Block a user