Comments 35
Отличный проект, я считаю, сугубо практический. И проблема решается насущная и машина на службе у человека и написание статьи не даст забыть почему выбраны те или иные решения.
Как по мне только слегка многовато воды в статье, - если свернуть куски кода, то вся статья читается как практический документ-заметка "сделал так, записал чтобы не забыть".
Прикольно, автор безусловно молодец, есть несколько но.
Имя бота написали, а вот backend похоже забыли запустить. Интересно будет посмотреть как оно будет, если хотя бы 1000 человек запустят бота
А по последнему пункту
П.с.3. Если вдруг ТГ накроется, думаю не сложно будет GUI набросать с подобным функционалом и запустить по сарафанному радио.
расстрою вас, просто набросать GUI мало, да и не скажу что просто совсем. Проще будет только давать ссылку на документ на диске руками
Спасибо за коммент. Про backend я так и писал - шо пока на работе пашет. Ну а сейчас, после публикации буду держать включенным). А про GUI поглядим, как посвободней на работе будет думаю занятся. Для виндовс не думаю что будет сложно - в голове уже крутится реализация, а для андроид пока ни разу не делал.
Отдельно сделаны сервисные команды, нужны для управления ботом. Сделаны буквенно-цифирным кодом что б никто не догадался.
Достаточно реализовать проверку на администратора бота по user_id
.
Если я правильно понял, то проблему с поиском по нужной папке (когда Вася выбрал одну папку, кто-то выбрал другую и в итоге Вася в обломе) можно без особых затрат решить без какой-либо дополнительной БД\таблицы в БД.
В том же aiogram есть замечательная вещь - FSM (т.н. "машина состояний"). Она позволит для каждого пользователя в рамках сессии хранить параметры.
Посмотрите, по ней документации много как и глобально, так и здесь, на хабре.
Отличный проект! Супер! Готов поделиться VPS/VDS для дальнейшей проверки гипотез.
Не совсем понятно, зачем нужен бот. А если просто положить файлы на я-диск ?
Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"
Ну или: с одной почты/диска выйди, на другой зайди, сам найди, ссылку скопируй...а тут ввел цифры, ентер нажал и результат)
Сделан для удобства и быстрого доступа. ТГ, вацапы всякие все время работают на компе/телефоне.
И базу все еще продолжаю наполнять, не все есть. благодаря этому проекту хоть порядок в залежах наведу)
Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"
Это нормально в файлах его хранить? Там не вносятся правки, уточнения, дополнения, изменения?
Ну, файлы понятны всем среднестатистическим пользователям. А нормативка - да - постоянно что-то меняют/корректируют/отменяют/новое вводят, довольно динамическая система. Тут только отслеживать и заменять устаревшее. А поскольку НТД очень много, то и пришлось реализовывать функцию, когда пользователь пришлет нормативку с целью подновления базы.
Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"
хттпс://корень.файлопомойки/госты/10705/
с одной почты/диска выйди, на другой зайди
Наверное, нужно либо настроить доступы, либо всем read only
Или свой nextcloud развернуть, если хочется рулить файлопомойкой по своим правилам.
А может быть просто расшарить ссылку на яндекс.диск и там поиском найти?
Может проще посмотреть в сторону calibe? На ней удобно делать библиотеки и поиск по тегам и доступ по opds.
"Планы на будущее: отправляешь боту акт входного контроля, а он оформляет сам журнал верификации…эх, мечты" - сделайте сайт с формами... заполняйте и генерируйте нужные формы. Ну или через Гугл документы, но тогда информация перестает быть конфиденциальной.
Не, там не такая задумка. Формы все есть, они известны. И даже озвученное в принципе реализуемо, Но - всё разрушает человеческий фактор - одни шаблоны не рушат, а других хлебом не корми - дай модифицировать. Ну и еще есть исключения из правил, например когда текста много и надо еще добавлять ячейку эксель для переноса на следующую страницу (например сегодня название материала в ячейке Е5, а завтра надо в Е5 и в Е6 разнести) и красивой печати на бумагу. И вот когда в папке 100-150 таких актов, да таких папок 100шт, то невольно задумываешься об автоматизации)) А возможно я не с той стороны смотрю: сделать форму GUI, заставить всех работать через нее, из нее заносить в БД, из базы формировать акты на выдачу)
Это выглядит как электронный документооборот.
Но - всё разрушает человеческий фактор
Это решается не на уровне питоновского кода и телеграм-ботов.
одни шаблоны не рушат, а других хлебом не корми - дай модифицировать
Наверное, каждый решает свои задачи.
когда в папке 100-150 таких актов, да таких папок 100шт,
В организации с такими оборотами еще в далекие 2000-е не напилили своих велосипедов на Delphi и VB?
сделали подобный бот на базу литературы на яндекс диске, но проблема обнаружилась что при запуске поиска бот ищет по всем папкам на диске, а там еще и личного мусора полно.
единственны
Раз уж вы стольких победили, не могли бы ещё и GitHub победить? Было бы интересно весь код посмотреть
Только, пока он случайно не победил вас, предупрежу - не опубликуйте токены, посмотрите в сторону .gitignore
Пока GitHub для меня сложноватая штука, код в принципе весь выложен, копипастой можно собрать, но раз весь надо, то тут и выложу. Под спойлером попытаюсь
Весь код (истинные сервисные не покажу, сделал замену)))
# pip install aiogram
# pip install yadisk
from aiogram import Bot, types
from aiogram.dispatcher import Dispatcher
from aiogram.utils import executor
import cfg_token
import yadisk
import glob, os
import sys
import time
from datetime import datetime
import sqlite3 as sl
bot = Bot(token=cfg_token.telebot_token)
dp = Dispatcher(bot)
codirovk = 'utf-8'
# токен яндекс диска
y = yadisk.YaDisk(token=cfg_token.ya_dsk_token)
# загружаемый файл должен содержать в своем имени
format_name_files =['ГОСТ', 'Гост', 'гост', 'GOST', 'Gost', 'gost',
'SP', 'sp', 'СП', 'сп', 'VSN', 'vsn', 'ВСН', 'всн', 'STO', 'sto', 'СТО', 'сто',
'RD', 'rd', 'РД', 'рд', 'Rd', 'Рд', 'Серия', 'СЕРИЯ', 'серия']
# загружаемый файл должен иметь расширение
format_ext_files = ['.pdf', '.doc', '.docx', '.rtf', '.djvu']
search_dir = 'GOST' #папка по умолчанию стартовая для поиска
def sget_base_bot(user_id, name_dir):
con = sl.connect('databasebot.db')
cur = con.cursor()
with con:
cur.execute("CREATE TABLE IF NOT EXISTS user_seadir(id INTEGER NOT NULL PRIMARY KEY, seadir TEXT)")
with con:
cur.execute("SELECT seadir FROM user_seadir WHERE id = " + str(user_id))
dat = cur.fetchone()
if name_dir == '': # блок запроса установленной папки
if dat is not None:
return dat[0]
else:
return 'GOST'
cur.execute('INSERT INTO user_seadir (id, seadir) values(?, ?)', (user_id, 'GOST'))
else: # блок установки папки поиска
if dat is None:
cur.execute('INSERT INTO user_seadir (id, seadir) values(?, ?)', (user_id, name_dir))
else:
cur.execute('UPDATE user_seadir SET seadir = ? WHERE id = ?', (name_dir, user_id))
return name_dir
cur.close()
con.close()
#----------------записать данные в рапорт---------------
def report_to_txt(str15):
try:
with open('Report.txt', 'a', encoding=codirovk) as file4:
file4.write(str15)
except Exception as e:
print('Ошибка: '+e)
#--------------- bot command user begin ----------------------------------------
@dp.message_handler(commands=['start', 'старт']) ## команда /start
async def process_start_command(message: types.Message):
await bot.send_message(message.from_user.id, "Прива! Я бот-помошник! Ищу НТД и выдаю их Вам")
await bot.send_message(message.from_user.id, "Работаю, правда, только в рабочее время в основном")
await bot.send_message(message.from_user.id, "Имейте ввиду - я различаю большие и малые буквы. А НТД загружаю по 1шт за раз")
await bot.send_message(message.from_user.id, "Также можно мне прислать файл НТД, которого у меня нет и я его добавлю")
await bot.send_message(message.from_user.id, "Дополнительную информацию можно получить по команде /help")
await bot.send_message(message.from_user.id, "Текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'GOST')+'. Её можно переключить командой (см. /help)')
await bot.send_message(message.from_user.id, "Введите запрос на НТД (можно только номер или часть наименования):")
@dp.message_handler(commands=['help', 'хелп']) ## команда /help
async def process_help_command(message: types.Message):
# тут не высттавляем папку поиска, берем ее из базы
await bot.send_message(message.from_user.id, "Введите запрос на НТД (можно только номер или часть наименования) и отправьте мне, а я поищу где-то и если найду, то отправлю Вам файл, по 1шт за раз.")
await bot.send_message(message.from_user.id, "Еще мне можно прислать то чего нет пока у меня, после проверки добавлю к себе и тогда оно будет:).")
await bot.send_message(message.from_user.id, "Доступны команды: /start (или /старт) - инфа при старте бота")
await bot.send_message(message.from_user.id, "/about (или /абут) - инфа о боте и разработчике")
await bot.send_message(message.from_user.id, "/status (или /статус) - выдает ответ о работе. Если ответа нет - не в сети")
await bot.send_message(message.from_user.id, "/sms MESSAGE (или /смс <текст сообщения>) — отправить любое сообщение MESSAGE разработчику бота.")
await bot.send_message(message.from_user.id, "/gost , /sp , /vsn, /sto, /rd (или русскими буквами)- Установка папки для поиска по виду НТД.")
await bot.send_message(message.from_user.id, "В данный момент включен поиск в папке: "+sget_base_bot(message.from_user.id, ''))
@dp.message_handler(commands=['GOST', 'gost', 'ГОСТ', 'гост']) ## команда /GOST
async def process_gost_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'GOST'))
@dp.message_handler(commands=['SP', 'sp', 'СП', 'сп']) ## команда /SP
async def process_sp_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'SP'))
@dp.message_handler(commands=['RD', 'rd', 'РД', 'рд']) ## команда /SP
async def process_rd_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'RD'))
@dp.message_handler(commands=['VSN', 'vsn', 'ВСН', 'всн']) ## команда /VSN
async def process_vsn_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'VSN'))
@dp.message_handler(commands=['STO', 'sto', 'СТО', 'сто']) ## команда /STO
async def process_sto_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'STO'))
@dp.message_handler(commands=['Serii', 'SERII', 'serii', 'Серии', 'СЕРИИ', 'серии', 'Серия', 'СЕРИЯ', 'серия']) ## команда /SERII
async def process_serii_command(message: types.Message):
await bot.send_message(message.from_user.id, "Установлена текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, 'Serii'))
@dp.message_handler(commands=['about', 'абут', 'разработчик']) ## команда /about
async def process_about_command(message: types.Message):
await bot.send_message(message.from_user.id, "Разработчик revalpam@ya.ru. Бот создан для оперативного получения документации в лично-производственных целях.")
@dp.message_handler(commands=['status', 'статус']) ## команда /status
async def process_status_command(message: types.Message):
await bot.send_message(message.from_user.id, "Работаю. Тут я. А шо такое? Вводите запрос и тисните Ентер:)")
await bot.send_message(message.from_user.id, "Текущая папка для поиска НТД: "+sget_base_bot(message.from_user.id, ''))
@dp.message_handler(commands=['sms', 'смс']) ## команда /sms сообщение разработчику
async def process_sms_command(message: types.Message):
report_to_txt('\nПользователь id'+str(message.from_user.id)+' отправил сообщение: '+message.text)
await bot.send_message(message.from_user.id, "Сообщение разработчику отправлено")
#--------------- bot command user end ------------------------------------------
#--------------- service command admin begin -----------------------------------
# выгружает файл отчета в облако и удаляет!!! его с диска
@dp.message_handler(commands=['rep123456789']) ## команда /rep отчет
async def process_rep_command(message: types.Message):
try:
# путь к загружаемым в облако файлам от пользователей
src = '/ReportBot'
if not y.is_dir(src):
y.mkdir(src)
destin_file = src+ '/Report-'+datetime.now().strftime("%d.%m.%Y-%H.%M.%S")+'.txt'
if y.is_file(destin_file):
y.remove(destin_file, permanently=True)
# грузим в облако файлы
if os.path.exists('Report.txt'):
y.upload('Report.txt', destin_file)
os.remove('Report.txt')
await bot.send_message(message.from_user.id, "Отчёт обработан")
except Exception as e:
print('Ошибка: '+e)
# показывает файл отчета с диска
@dp.message_handler(commands=['view-rep123456789']) ## команда показать отчет
async def process_view_report_command(message: types.Message):
try:
if os.path.exists('Report.txt'):
with open('Report.txt', encoding=codirovk) as f5:
ch_file = len(f5.read())
if ch_file<2400:
with open('Report.txt', 'r', encoding=codirovk) as f5:
await bot.send_message(message.from_user.id, f5.read())
else:
await bot.send_message(message.from_user.id, "Отчёт слишком большой. Лучше смотреть в облаке после команды /rep")
else:
await bot.send_message(message.from_user.id, "Отчёт не создавался пока")
except Exception as e:
print('Ошибка: '+e)
##@dp.message_handler(commands=['stop']) ## команда /stop работает криво - много ошибок иде показывает
##async def process_stop_command(message: types.Message):
## await bot.send_message(message.from_user.id, "Останавливаюсь.")
## sys.exit()
#--------------- service command admin end -------------------------------------
@dp.message_handler(content_types=['text']) ## получаем сообщение от юзера
async def get_text_messages(message: types.Message):
search_dir = sget_base_bot(message.from_user.id, '')
report_to_txt('\nПользователь id'+str(message.from_user.id)+' сделал запрос на поиск в папке ' + search_dir +': '+message.text)
await bot.send_message(message.from_user.id, 'Запускаю процесс поиска в папке ' + search_dir +' : '+message.text)
if y.check_token():
if not y.is_dir('/'+search_dir):
await bot.send_message(message.from_user.id, 'Папка ' + search_dir +' не обнаружена. Шо-то поломалось. Извините.')
else:
Spis = []
for item in y.listdir(search_dir):
if message.text in item['name']:
if len(Spis) < 7:
await bot.send_message(message.from_user.id, 'Обнаружен документ: '+item['name'])
Spis.append(item['name'])
if len(Spis) == 0:
await bot.send_message(message.from_user.id, 'Извините, пока такого документа в папке '+ search_dir +' не нашлось.')
await bot.send_message(message.from_user.id, 'Но если Вы мне его сюда скинете, после проверки я его добавлю.')
if len(Spis) == 1:
# ------------даем ссылку на файл-------------------
y.publish('/'+search_dir+'/'+Spis[0]) # делаем публичный файл
# шлем ссылку
await bot.send_message(message.from_user.id, y.get_meta('/'+search_dir+'/'+Spis[0]).public_url)
if len(Spis) > 1:
await bot.send_message(message.from_user.id, 'Найдено документов: '+str(len(Spis)) + '. Уточните запрос:')
else:
await bot.send_message(message.from_user.id, 'Извините по каким-то причинам диск не доступен. Попробуйте в другой раз')
@dp.message_handler(content_types=['document']) # получаем файл от юзера
async def handle_file(message):
try:
# если файл такой как надо, то качаем
str_nam_file = str(message.document.file_name)
len_str_nf = len(str_nam_file)
if str_nam_file.endswith(tuple(format_name_files), 0, len_str_nf) and str_nam_file.endswith(tuple(format_ext_files), 0, len_str_nf):
file_id = message.document.file_id
file = await bot.get_file(file_id)
file_path = file.file_path
# ------------Вариант загрузки файлов в яндекс-облако------------
# путь к загружаемым в облако файлам от пользователей
if not y.is_dir('/DownloadBot'):
y.mkdir('/DownloadBot')
src = '/DownloadBot/'+ message.document.file_name
print(src)
# грузим в облако файл от пользователя
if y.is_file(src): # если такой файл есть то яндя даст ошибку, поэтому: вот
src = '/DownloadBot/Double-'+datetime.now().strftime("%d.%m.%Y-%H.%M.%S")+'-'+ message.document.file_name
y.upload(await bot.download_file(file_path), src)
await bot.send_message(message.from_user.id, 'Загрузил. Спасибо. После проверки добавлю в свою базу:)')
# сделать запись после загрузки в файл Report.txt
report_to_txt('\nПользователь id'+str(message.from_user.id)+' прислал файл: '+message.document.file_name)
else:
await bot.send_message(message.from_user.id, 'Простите, но присланный Вами файл не содержит в имени тип НТД (ГОСТ, СП, ВСН и т.д.) и/или не подходит по формату, нужен .pdf или .doc')
except Exception as e:
print('Ошибка: '+e)
await bot.send_message(message.from_user.id, 'Я наверное не смогу загрузить, шо-то сломалось и выдает ошибку: '+e)
if __name__ == '__main__':
executor.start_polling(dp)
## executor.start_polling(dp, skip_updates=True)
##Параметр skip_updates=True позволяет пропустить накопившиеся входящие сообщения, если они нам не важны
Дабы не закидывать в ЯД файлы вручную, набросал скрипт. Использует он тот же файл с токенами, что и бот cfg_token. Возле скрипта кладу файлы, внутри в нем отмечаю папку куда надо закинуть (правлю или как вариант заготовить на каждую отдельно) и запускаю его. После загрузки он удаляет обработанные файлы.
код скрипта наполнения папки на ЯД
import cfg_token
import yadisk
import glob, os, shutil
import sys
import time
from datetime import datetime
# токен яндекс диска
y = yadisk.YaDisk(token=cfg_token.ya_dsk_token)
# загружаемый файл должен иметь расширение
format_move_files = ('.pdf', '.doc', '.docx', '.djvu', '.rar')#, '.rtf') '.zip', - зипы плохо обраб. ЯД
up_dir = 'Serii' #папка для хранения
## 'GOST' 'SP' 'VSN' 'STO'
def up_to_dir(file_name):
try:
# путь к загружаемым в облако файлам от пользователей
src = '/'+up_dir
dst = src+'/'+file_name
if not y.is_dir(src): # если папки нет, то создать
y.mkdir(src)
# грузим в облако файл
if os.path.exists(file_name):
if y.is_file(dst): # если такой файл есть то яндя даст ошибку, поэтому: вот
dst = src+'/Double-'+datetime.now().strftime("%d.%m.%Y-%H.%M.%S")+'-'+ file_name
y.upload(file_name, dst)
print('Загружен файл: '+file_name+' в облако '+dst)
os.remove(file_name)
print('Удален файл: '+file_name)
except Exception as e:
print('Ошибка: ', e)
k_file=0
file_in_dir = os.listdir(os.getcwd())
for file in file_in_dir:
if file.endswith(format_move_files, 0, len(file)): #'.pdf' if file.endswith('.pdf'):tuple(
print('Обрабатываю файл: ',file)
up_to_dir(file)
k_file=k_file+1
print('-----------------------------------------------')
else:
continue
print('Обработано файлов: '+str(k_file))
input("Работа завершена. Тисни ентер.")
Бот уже неживой, как я понял?
Бот для телеграмма, использующий Яндекс.Диск (Python)