Начни зарабатывать на софте: cоздание mini-digital-business

от автора

Сидя на freelance видел много раз задачи по сбору БД. Чаще всего просят собрать информацию о компаниях или специфические запросы на Google, Yandex картах.

Есть спрос, давайте создавать предложения, но обо всём по порядку.

В данной статье предлагаю разработать Telegram bot, который будет принимать название города (в котором будет производиться поиск) и запрос (по которому будет производиться поиск.

Например: Бар, Кофе, Ресторан и т.д.). Реализуем возможность делать donation, оплачивать услугу по сбору БД и отправлять клиентам на почту БД.

Используемые технологии:

  1. Telegram API;
  2. Yandex API;
  3. Payments API.

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

Эта программа не является коммерческой. Реализована для примера, т.к. правилами Яндекса запрещено сохранять данные

О правилах можно сказать то же, что и о законах: их многочисленность доказывает не столько соблюдение, сколько нарушение их. ©Дюбэ

Подготовка

Для удобства разделим проект на 3 файла. Создадим bot.py, yandex.py, send_email.py.

Реализация

Yandex.py

  • Импортируем библиотеки:
    # -*- coding: utf-8 -*- import requests import xlwt, xlrd from xlutils.copy import copy as xlcopy

  • Для обращение к Yandex API нужно получить ключ, по которому будут Вас идентифицировать. Ключи для использования maps api и location api разные.
    Задаем api ключи:
    apikey = '*******-***-****-****-************' apikey_location = '********-****-****-****-************'

  • Получение общего кол-во найденных объектов:
    def sum_taken_object(all_informations):     try:         found = str(all_informations['properties']['ResponseMetaData']['SearchResponse']['found'])     except KeyError:         found = '-'     return found

  • Получение всей информации:
    def get_all_infomations(text, city):     value_low_upp, point = get_location(city)     low = value_low_upp[0].split(',')     upp = value_low_upp[1].split(',')     informations = requests.get('https://search-maps.yandex.ru/v1/?'                                 'apikey='+apikey+'&'                                 'text='+text+'&'                                 'lang=ru_RU&'                                 'll='+point[0]+'&'                                 'bbox='+low[1]+','+low[0]+'~'+upp[1]+','+upp[0]+'&'                                 'results=500')     return informations.json()

    В запросе requests.get параметр bbox указывает на координаты области поиска, а ll на центр области поиска. Можно указать только параметр ll без bbox, если нужны результаты только в центре города. О дополнительных возможностях можете почитать тут.

    Обратите внимание, что больше 500 объектов запрос не вернет.

    Если будет интересна реализация сбора всех объектов превышающих кол-во 500, отпишитесь. Сделаю отдельный пост.

  • Получение долготы и широты:
    def get_location(city):     location = requests.get('https://geocode-maps.yandex.ru/1.x/?'                             'apikey='+apikey_location+'&'                             'geocode='+city.title()+'&'                             'format=json')     loc = location.json()     loc = loc['response']['GeoObjectCollection']['featureMember']     point = loc[0]['GeoObject']['Point']['pos'].split()     value_lower = loc[0]['GeoObject']['boundedBy']['Envelope']['lowerCorner'].split()     value_upper = loc[0]['GeoObject']['boundedBy']['Envelope']['upperCorner'].split()     value_low_upp = [value_lower[1]+','+value_lower[0], value_upper[1]+','+value_upper[0]]     return value_low_upp, point

    Возвращает долготу и ширину координаты области и центра.

  • Получаем ограниченное кол-во объектов:
    def get_information_limit(text,city):     value_low_upp, point = get_location(city)     low = value_low_upp[0].split(',')     upp = value_low_upp[1].split(',')     info = requests.get('https://search-maps.yandex.ru/v1/?'                         'apikey='+apikey+'&'                         'text='+text+'&'                         'lang=ru_RU&'                         'll='+point[0]+'&'                         'bbox='+low[1]+','+low[0]+'~'+upp[1]+','+upp[0]+'&'                         'results=5&'                         'skip=5')     information = info.json()     information = information['features']     list = {}     i = 1     for key in information:         try:             coordinates = str(key['geometry']['coordinates'])         except KeyError:             coordinates = '-'         try:             name = str(key['properties']['CompanyMetaData']['name'])         except KeyError:             name = '-'         try:             address = str(key['properties']['CompanyMetaData']['address'])         except KeyError:             address = '-'         try:             url = str(key['properties']['CompanyMetaData']['url'])         except KeyError:             url = '-'         try:             phones = key['properties']['CompanyMetaData']['Phones']         except KeyError:             phones = '-'         try:             hours = str(key['properties']['CompanyMetaData']['Hours']['text'])         except KeyError:             hours = '-'          for k in phones:             try:                 phones = k['formatted']             except TypeError:                 pass          list['object'+str(i)] = {'coordinates':coordinates,'name':name,'address':address,'url':url,'phones':phones,'hours':hours}         i += 1     return list

    Возвращает словарь с информацией по 5 объектам. Если хотите изменить кол-во объектов, тогда измените значение двух переменных resul, skip в запросе requests.get.

  • Запись базы данных в Excel файл:
    def write_exl(text, city_name):     try:         name_excel_BD = creat_excel_file(name='{}_{}'.format(text, city_name))         read_book = xlrd.open_workbook(name_excel_BD)  # Открываем исходный документ         write_book = xlcopy(read_book)  # Копируем таблицу в память, в неё мы ниже будем записывать         write_sheet = write_book.get_sheet(0)  # Будем записывать в первый лист         # index = read_book.sheet_by_index(0).nrows # Номер последней строки         url_maps = 'https://yandex.ru/maps/org/'         info = get_all_infomations(text=text, city=city_name)          for number, inf in enumerate(info['features']):             try:                 name_object = inf['properties']['CompanyMetaData']['name']             except KeyError:                 name_object = '-'             try:                 address = inf['properties']['CompanyMetaData']['address']             except KeyError:                 address = '-'             try:                 time_work = inf['properties']['CompanyMetaData']['Hours']['text']             except KeyError:                 time_work = '-'             try:                 id_organization = inf['properties']['CompanyMetaData']['id']             except KeyError:                 id_organization = '-'              write_sheet.write(int(number), 0, city_name)  # Город             write_sheet.write(int(number), 1, name_object)  # Название объекта             write_sheet.write(int(number), 2, address)  # Адрес             write_sheet.write(int(number), 3, time_work)  # Время работы             write_sheet.write(int(number), 4, url_maps + str(id_organization))  # Url на карте         write_book.save(name_excel_BD)  # Сохраняем таблицу         return True, name_excel_BD     except TypeError or KeyError as err:         return False

  • Создание файла Excel:
    def creat_excel_file(name):     book = xlwt.Workbook('utf8')     book.add_sheet('База_{}'.format(name))     book.save('BD_{}.xls'.format(name))     return 'BD_{}.xls'.format(name)

Send_email.py

  • Импортируем библиотеки:
    #!/usr/bin/env python #   coding: utf8  from smtplib import SMTP_SSL from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email import encoders import os

  • Функция для отправки писем с вложением:
    def send_mail(name_file, to_address):     filepath = name_file     address_from = "********@gmail.com"     address_to = to_address     password = '************'     mail_adr = 'smtp.gmail.com'     mail_port = 465      # Compose attachment     part = MIMEBase('application', "octet-stream")     part.set_payload(open(filepath, "rb").read())     encoders.encode_base64(part)     part.add_header('Content-Disposition', "attachment", filename="%s" % os.path.basename(filepath))      # Compose message     msg = MIMEMultipart()     msg['From'] = address_from     msg['To'] = address_to     msg.attach(part)      # Send mail     smtp = SMTP_SSL(mail_adr)     smtp.set_debuglevel(1)     smtp.connect(host=mail_adr, port=mail_port)     smtp.login(address_from, password)     smtp.sendmail(address_from, address_to, msg.as_string())     smtp.quit()

    Если будете реализовывать через Gmail, Вам нужно зайти в личный аккаунт на Google – Безопасность и в «Ненадежные приложения, у которых есть доступ к аккаунту» нужно отключить, иначе отправка писем будет блокироваться на стороне сервера Gmail.

Bot.py

  • Импортируем библиотеки:
    # -*- coding: utf-8 -*- import telebot from telebot.types import LabeledPrice import re  import yandex from send_email import send_mail

  • Получаем token и добавляем в переменную:
    token = '*********:**********************************' bot = telebot.TeleBot(token)

  • Создаем обработку команд:
    @bot.message_handler(commands=['start', 'donation']) def send_welcom(message):     if message.text == '/start':         keyboard = telebot.types.InlineKeyboardMarkup()         keyboard.row(telebot.types.InlineKeyboardButton('Получить базу', callback_data='bd_get'))         bot.send_message(message.chat.id, 'Выберите действие: ', reply_markup=keyboard)      if message.text == '/donation':         bot.send_invoice(message.chat.id,                          title='Donation',                          description='Можешь отправить финансовую благодарность.',                          invoice_payload='donation',                          provider_token='*********:TEST:*******',                          currency='RUB',                          prices=[LabeledPrice(label='Donation', amount=10000)],                          start_parameter='pay_start',                          photo_url='https://cdn.imgbin.com/22/0/5/imgbin-donation-computer-icons-'                                    'fundraising-justgiving-charitable-organization-donation-'                                    '5Yehm9UecF2cRWrqtms4e6emn.jpg',                          photo_height=512,  # !=0/None or picture won't be shown                          photo_width=512,                          photo_size=512,                          is_flexible=False)

    Первый IF обрабатывает запрос /start, второй IF обрабатывает запрос /donation.
    В параметре invoice_payload задаем название, по которому будем определяться подтверждение оплаты.

    В bot.send_invoice нужно указать token используемого провайдера платежей. Как его получить, написано здесь.

    Для разработки советую использовать тестовый token провайдера. При его использовании функционал сохраняется, но деньги не снимаются. В дальнейшем Вам потребуется только заменить на реальный token.
    Тестовый: 123:TEST:XXXX
    Реальный: 123:LIVE:XXXX

  • Данный декоратор необходим для подтверждения наличия заказа:
    @bot.pre_checkout_query_handler(func=lambda query: True) def checkout(message):     bot.answer_pre_checkout_query(message.id, ok=True,                                   error_message="Инопланетяне пытались украсть CVV вашей карты, "                                                 "но мы успешно защитили ваши учетные данные. "                                                 "Попробуй расплатиться через несколько минут, "                                                 "нам нужен небольшой отдых.")

    Т.к. у нас digital услуга, она всегда в наличии по этому, параметр ok всегда True. Без данного подтверждения оплата не будет проходить у клиента.

    Данный декоратор полезен если у Вас, например, магазин обуви. Клиент заходит оформить заказ, нажимает оплатить и в этот момент Вам на сервер приходит запрос, который обрабатывается этой функцией. Если на складе имеется данный товар, то параметр ok=True иначе задаете значение False и клиент видит всплывающее окно с текстом, который указан в параметре error_message.

  • Декоратор подтверждает оплату заказа:
    @bot.message_handler(content_types=['successful_payment']) def got_payment(message):     if message.json['successful_payment']['invoice_payload'].split(',')[0] == 'buy':         email = message.json['successful_payment']['order_info']['email']         bot.send_message(message.chat.id,                          'Ураааа! Спасибо за оплату на сумму: `{} {}`.\n'                          'Вам на почту (`{}`) отправленно письмо с базой данных.\n\n' \                          'По всем возникшим вопросам обращайтесь @имя_админа'\                          .format(message.successful_payment.total_amount / 100,                                  message.successful_payment.currency,                                  email),                         parse_mode='Markdown')         request_text = message.json['successful_payment']['invoice_payload'].split(',')[1]         city = message.json['successful_payment']['invoice_payload'].split(',')[2]         write_in_BD, name_excel_BD = yandex.write_exl(text=request_text, city_name=city)         if write_in_BD == True:             send_mail(name_file=name_excel_BD, to_address=email)      elif message.json['successful_payment']['invoice_payload'] == 'donation':         bot.send_video(message.chat.id, 'https://media0.giphy.com/media/QAsBwSjx9zVKoGp9nr/giphy.gif')

  • Декоратор обрабатывает нажатые кнопки InlineKeyboardMarkup:
    @bot.callback_query_handler(func=lambda call: True) def callback_key(message):     if message.data == 'bd_get':         input_city(message)     elif re.search(r'bd_yes/',message.data):         location = re.sub('bd_yes/','',message.data)         bot.answer_callback_query(message.id, text=location, show_alert=False)         input_text(message, city=location)     elif message.data == 'bd_no':         input_city(message)     elif re.search(r'pay', message.data):         info_get_bd_limit = re.split(r'/',message.data)         sent_text = info_get_bd_limit[1]         sent_city = info_get_bd_limit[2]         found = info_get_bd_limit[3]         bot.answer_callback_query(message.id, text='Оплата', show_alert=False)         pay(message, text=sent_text, city=sent_city, found=found)

  • Функция отправляет счет на оплату:
    def pay(message, text, city, found):     bot.send_invoice(message.from_user.id,                      title='База данных',                      description='Оплата базы данных по запросу.\n'                                  'Текст: '+text+'\nГород: '+city+'\nКол-во объектов: '+found,                      invoice_payload='buy,{},{}'.format(text, city),                      provider_token='*********:TEST:*******',                      currency='RUB',                      prices=[LabeledPrice(label='База данных', amount=20000)],                      start_parameter='pay_start',                      photo_url='https://encrypted-tbn0.gstatic.com/images?q=tbn:'                                'ANd9GcRVUs3eGt4U9YSXZrsbOkJoNEdpcYUdq0vEzM-ci_oIxEWs1FK0',                      photo_height=300,                      photo_width=300,                      photo_size=300,                      need_email=True,                      is_flexible=False)

    Параметр need_email необходим, так как нужно запросить у клиента почту, на которую будем отправлять БД.

  • Функция запрашивает название города, по которому будет производиться поиск:
    def input_city(message):     bot.send_message(message.from_user.id, 'Введите город:')     bot.register_next_step_handler_by_chat_id(message.from_user.id, get_city)     bot.answer_callback_query(message.id, text='Введите город', show_alert=False)

  • Функция отправляет клиенту геолокацию, для уточнения найденного города:
    def get_city(message):     get_location, get_location_point = yandex.get_location(message.text)     keyboard = telebot.types.InlineKeyboardMarkup()     keyboard.row(telebot.types.InlineKeyboardButton('Да', callback_data='bd_yes/'+message.text),                  telebot.types.InlineKeyboardButton('Нет', callback_data='bd_no'))     bot.send_location(message.chat.id, get_location_point[1], get_location_point[0], reply_markup=keyboard)

  • Функция запрашивает ввод объекта, который хочет искать клиент:
    def input_text(message, city=None):     global location_city     location_city = city     info_message_id = bot.send_message(message.from_user.id, 'Введите объект (например бар): ')     bot.register_next_step_handler_by_chat_id(message.from_user.id, get_bd_limit)

  • Функция отправляет ограниченное кол-во информации о найденных объектах и предлагает оплатить полную БД:
    def get_bd_limit(message):     information = yandex.get_information_limit(message.text, location_city)     if information:         found = yandex.sum_taken_object(yandex.get_all_infomations(message.text, location_city))         bot.send_message(message.from_user.id, 'Бот работает в бесплатном режиме.\n'                                                'Будет выведено только 5 первых попавшихся результатов.')         i = 1         text = ''         for key, value in information.items():             name = value['name']             address = value['address']             url = value['url']             phones = value['phones']             hours = value['hours']             text = text + '' + str(i) + ') Название: '+name+'\nАдрес: '+address+'\nСайт: '+url+\                                         '\nТелефон: '+phones+'\nВремя работы: '+hours+'\n----------\n'             i += 1         bot.send_message(message.from_user.id, text, disable_web_page_preview=True)          keyboard = telebot.types.InlineKeyboardMarkup()         keyboard.row(telebot.types.InlineKeyboardButton('Оплатить 200 руб.',                                                         callback_data='pay/' + message.text+'/'+location_city+'/'+found))         bot.send_message(message.from_user.id, 'Всего найдено объектов по вашему запросу: ' + found+''                                                 '\n\nВсю базу вы можете получить на почту.', reply_markup=keyboard)     else:         bot.send_message(message.from_user.id, 'Ничего не найдено по данному запросу!') 

  • В конце кода добавляем по рекомендациям Telegram:
    while True:     try:         bot.polling(none_stop=True)     except Exception as e:         time.sleep(15)

Сейчас Telegram поддерживает 8 платежных систем. Для российского рынка самые ходовые это Яндекс Касса и Сбербанк. Советую использовать Tranzzo, т.к. с помощью него можно сохранять данные карты, использовать отпечаток пальца для оплат в течение 5 часов после подтверждения паролем.

При использовании тестового token от Сбербанк процесс выставление счет зависал, а при использовании Яндекс Кассы все хорошо. Не знаю с чем это связанно, но факт.

image
image

Заключение

Данный пост предназначен не только для того, чтобы показать возможности реализации mini-digital-business, но и для того чтобы расшевелить в тебе it предпринимателя, начать генерировать новые идеи где можно было бы применить данный функционал.

Что заработал, то и получил: ударник — хлеб, а лодырь — ничего.

ссылка на оригинал статьи https://habr.com/ru/post/495594/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *