Pull to refresh

Comments 35

Отличный проект, я считаю, сугубо практический. И проблема решается насущная и машина на службе у человека и написание статьи не даст забыть почему выбраны те или иные решения.

Как по мне только слегка многовато воды в статье, - если свернуть куски кода, то вся статья читается как практический документ-заметка "сделал так, записал чтобы не забыть".

Прикольно, автор безусловно молодец, есть несколько но.

Имя бота написали, а вот backend похоже забыли запустить. Интересно будет посмотреть как оно будет, если хотя бы 1000 человек запустят бота

А по последнему пункту

П.с.3. Если вдруг ТГ накроется, думаю не сложно будет GUI набросать с подобным функционалом и запустить по сарафанному радио.

расстрою вас, просто набросать GUI мало, да и не скажу что просто совсем. Проще будет только давать ссылку на документ на диске руками

Спасибо за коммент. Про backend я так и писал - шо пока на работе пашет. Ну а сейчас, после публикации буду держать включенным). А про GUI поглядим, как посвободней на работе будет думаю занятся. Для виндовс не думаю что будет сложно - в голове уже крутится реализация, а для андроид пока ни разу не делал.

Отдельно сделаны сервисные команды, нужны для управления ботом. Сделаны буквенно-цифирным кодом что б никто не догадался.

Достаточно реализовать проверку на администратора бота по user_id.

Тоже думал над этим - через отдельную таблицу БД с правами допуска.

Зачем ещё БД? Права доступа могут в этой же таблице лежать.

Если я правильно понял, то проблему с поиском по нужной папке (когда Вася выбрал одну папку, кто-то выбрал другую и в итоге Вася в обломе) можно без особых затрат решить без какой-либо дополнительной БД\таблицы в БД.
В том же aiogram есть замечательная вещь - FSM (т.н. "машина состояний"). Она позволит для каждого пользователя в рамках сессии хранить параметры.
Посмотрите, по ней документации много как и глобально, так и здесь, на хабре.

Ого. Я про такую штуку и не знал. Спасибо, гляну.

А вообще это делается отдельными сессиями телеграмм. Посмотрите режимы записи и в частности примеры с инлайн Клавой.

Отличный проект! Супер! Готов поделиться VPS/VDS для дальнейшей проверки гипотез.

надо обдумать как это делается)

Не совсем понятно, зачем нужен бот. А если просто положить файлы на я-диск ?

  • Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"

  • Ну или: с одной почты/диска выйди, на другой зайди, сам найди, ссылку скопируй...а тут ввел цифры, ентер нажал и результат)

    Сделан для удобства и быстрого доступа. ТГ, вацапы всякие все время работают на компе/телефоне.

    И базу все еще продолжаю наполнять, не все есть. благодаря этому проекту хоть порядок в залежах наведу)

Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"

Это нормально в файлах его хранить? Там не вносятся правки, уточнения, дополнения, изменения?

Ну, файлы понятны всем среднестатистическим пользователям. А нормативка - да - постоянно что-то меняют/корректируют/отменяют/новое вводят, довольно динамическая система. Тут только отслеживать и заменять устаревшее. А поскольку НТД очень много, то и пришлось реализовывать функцию, когда пользователь пришлет нормативку с целью подновления базы.

Нет какой-то системы типа консультанта, где все правки и изменения уже учтены? Самому поддерживать в актуальном состоянии довольно проблематично

Наверное есть. Но так и так нормативку таскать с объекта на объект, зато предоставить доступ к своим залежам теперь легко: послать к тг-боту)

Вообще есть – мне в этом году пару раз предлагали по охране труда, например. Денег стоит столько же, сколько Консультант и подобное, но функционал урезанный… Бесплатно не встречал)))

@Kraleks– отличный проект! ??

Звонит руководитель проекта с полей или из леса: "Мы тут глянули проект, там гост 10705, срочно пришли мне его"

хттпс://корень.файлопомойки/госты/10705/

с одной почты/диска выйди, на другой зайди

Наверное, нужно либо настроить доступы, либо всем read only

Или свой nextcloud развернуть, если хочется рулить файлопомойкой по своим правилам.

А может быть просто расшарить ссылку на яндекс.диск и там поиском найти?

может быть), но это не наш метод, это не спортивно)

Может проще посмотреть в сторону calibe? На ней удобно делать библиотеки и поиск по тегам и доступ по opds.

интересная система

"Планы на будущее: отправляешь боту акт входного контроля, а он оформляет сам журнал верификации…эх, мечты" - сделайте сайт с формами... заполняйте и генерируйте нужные формы. Ну или через Гугл документы, но тогда информация перестает быть конфиденциальной.

Не, там не такая задумка. Формы все есть, они известны. И даже озвученное в принципе реализуемо, Но - всё разрушает человеческий фактор - одни шаблоны не рушат, а других хлебом не корми - дай модифицировать. Ну и еще есть исключения из правил, например когда текста много и надо еще добавлять ячейку эксель для переноса на следующую страницу (например сегодня название материала в ячейке Е5, а завтра надо в Е5 и в Е6 разнести) и красивой печати на бумагу. И вот когда в папке 100-150 таких актов, да таких папок 100шт, то невольно задумываешься об автоматизации)) А возможно я не с той стороны смотрю: сделать форму GUI, заставить всех работать через нее, из нее заносить в БД, из базы формировать акты на выдачу)

Это выглядит как электронный документооборот.

Но - всё разрушает человеческий фактор

Это решается не на уровне питоновского кода и телеграм-ботов.

одни шаблоны не рушат, а других хлебом не корми - дай модифицировать

Наверное, каждый решает свои задачи.

когда в папке 100-150 таких актов, да таких папок 100шт,

В организации с такими оборотами еще в далекие 2000-е не напилили своих велосипедов на Delphi и VB?

сделали подобный бот на базу литературы на яндекс диске, но проблема обнаружилась что при запуске поиска бот ищет по всем папкам на диске, а там еще и личного мусора полно.
единственны

search_dir = 'GOST'
y.is_dir('/'+search_dir):
	for item in y.listdir(search_dir):
		...

Раз уж вы стольких победили, не могли бы ещё и 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("Работа завершена. Тисни ентер.")

Бот уже неживой, как я понял?

Работает, когда я на работе. Задеплоил в виртуалбокс на TinyCore.

Ну и в связи с выходом обновлений пакетов, встретился с "неработой" бота - aiogram переделали, поэтому актуально указывать версию:

pip install aiogram==2.25.1 

pip install yadisk==1.3.0

Sign up to leave a comment.

Articles