Прошлая статья оказалась провальной, именно поэтому хочу улучшить ситуацию путем написания продолжения. Ну буду отвлекаться на самые простые вещи, приступим!

Ну для начала, если вы из прошлой статьи, установите PyCharm. Думаю с установкой сложностей не возникнет, во всяком случае я на это надеюсь.

Открываем PyCharm, создаем файл main.py и начинаем писать.

Первые приготовления
  1. Импортируем необходимые библиотеки:

    from aiogram import Bot, Dispatcher, executor, types
    from aiogram.contrib.fsm_storage.memory import MemoryStorage
    from aiogram.dispatcher import FSMContext
    from aiogram.dispatcher.filters.state import State, StatesGroup
    from aiogram.types import Message
    import logging
    import sqlite3
  2. Обьявляем переменные

    API_TOKEN = 'ТОКЕН'
    ADMIN = ваш user-id. Узнать можно тут @getmyid_bot
    
    kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
    kb.add(types.InlineKeyboardButton(text="Рассылка"))
    kb.add(types.InlineKeyboardButton(text="Добавить в ЧС"))
    kb.add(types.InlineKeyboardButton(text="Убрать из ЧС"))
    kb.add(types.InlineKeyboardButton(text="Статистика"))
  3. Инициализируем проект

    logging.basicConfig(level=logging.INFO)
    storage = MemoryStorage()
    bot = Bot(token=API_TOKEN)
    dp = Dispatcher(bot, storage=storage)
  4. Создаем Базу Данных

    conn = sqlite3.connect('db.db')
    cur = conn.cursor()
    cur.execute("""CREATE TABLE IF NOT EXISTS users(user_id INTEGER, block INTEGER);""")
    conn.commit()
  5. Обьявляем States

    class dialog(StatesGroup):
    	spam = State()
      blacklist = State()
      whitelist = State()

Это первые приготовления, теперь мы готовы начинать писать основную логику

Обработка команды "/start"
@dp.message_handler(commands=['start'])
async def start(message: Message):
  cur = conn.cursor()
  cur.execute(f"SELECT block FROM users WHERE user_id = {message.chat.id}")
  result = cur.fetchone()
  if message.from_user.id == ADMIN:
    await message.answer('Добро пожаловать в Админ-Панель! Выберите действие на клавиатуре', reply_markup=kb)
  else:
      if result is None:
        cur = conn.cursor()
        cur.execute(f'''SELECT * FROM users WHERE (user_id="{message.from_user.id}")''')
        entry = cur.fetchone()
        if entry is None:
          cur.execute(f'''INSERT INTO users VALUES ('{message.from_user.id}', '0')''')
          conn.commit()
          await message.answer('Привет')
      else:
        await message.answer('Ты был заблокирован!')

Тут мы добавляем три кнопки в админ-панель

Давайте напишем функцию обработки кнопки "Рассылка"

Функция Рассылки

Теперь давайте обработаем кнопку "Рассылка"

@dp.message_handler(content_types=['text'], text='Рассылка')
async def spam(message: Message):
  await dialog.spam.set()
  await message.answer('Напиши текст рассылки')

Но тут мы только спрашиваем у админа что за текст рассылки

Теперь давайте обработаем этот текст и отправим его пользователям

@dp.message_handler(state=dialog.spam)
async def start_spam(message: Message, state: FSMContext):
  if message.text == 'Назад':
    await message.answer('Главное меню', reply_markup=kb)
    await state.finish()
  else:
    cur = conn.cursor()
    cur.execute(f'''SELECT user_id FROM users''')
    spam_base = cur.fetchall()
      for z in range(len(spam_base)):
        await bot.send_message(spam_base[z][0], message.text)
        await message.answer('Рассылка завершена', reply_markup=kb)
        await state.finish()

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

Теперь давайте сделаем добавление пользователей в ЧС

Функция Блокировки

Сейчас мы будем обрабатывать кнопку "Добавить в ЧС"

@dp.message_handler(content_types=['text'], text='Добавить в ЧС')
async def hanadler(message: types.Message, state: FSMContext):
  if message.chat.id == ADMIN:
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    keyboard.add(types.InlineKeyboardButton(text="Назад"))
    await message.answer('Введите id пользователя, которого нужно заблокировать.\nДля отмены нажмите кнопку ниже', reply_markup=keyboard)
    await dialog.blacklist.set()

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

@dp.message_handler(state=dialog.blacklist)
async def proce(message: types.Message, state: FSMContext):
  if message.text == 'Назад':
    await message.answer('Отмена! Возвращаю назад.', reply_markup=kb)
    await state.finish()
  else:
    if message.text.isdigit():
      cur = conn.cursor()
      cur.execute(f"SELECT block FROM users WHERE user_id = {message.text}")
      result = cur.fetchall()
      if len(result) == 0:
        await message.answer('Такой пользователь не найден в базе данных.', reply_markup=kb)
        await state.finish()
      else:
        a = result[0]
        id = a[0]
        if id == 0:
          cur.execute(f"UPDATE users SET block = 1 WHERE user_id = {message.text}")
          conn.commit()
          await message.answer('Пользователь успешно добавлен в ЧС.', reply_markup=kb)
          await state.finish()
          await bot.send_message(message.text, 'Ты был забанен Администрацией')
        else:
          await message.answer('Данный пользователь уже получил бан', reply_markup=kb)
          await state.finish()
    else:
      await message.answer('Ты вводишь буквы...\n\nВведи ID')

Вот теперь, после того как Вы отправите ему ID пользователя, он проверить его, и если нашел этого пользователя в Базе не забаненый, то банит! В остальных случаях возвращает в главное меню

Теперь давайте будем Удалять пользователей из ЧС

Функция Разблокировки

Для начала, по стандарту, обработаем кнопку

@dp.message_handler(content_types=['text'], text='Убрать из ЧС')
async def hfandler(message: types.Message, state: FSMContext):
  cur = conn.cursor()
  cur.execute(f"SELECT block FROM users WHERE user_id = {message.chat.id}")
  result = cur.fetchone()
  if result is None:
    if message.chat.id == ADMIN:
      keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
      keyboard.add(types.InlineKeyboardButton(text="Назад"))
      await message.answer('Введите id пользователя, которого нужно разблокировать.\nДля отмены нажмите кнопку ниже', reply_markup=keyboard)
      await dialog.whitelist.set()

И снова бот ничего не делает, сейчас исправим это!

@dp.message_handler(state=dialog.whitelist)
async def proc(message: types.Message, state: FSMContext):
  if message.text == 'Отмена':
    await message.answer('Отмена! Возвращаю назад.', reply_markup=kb)
    await state.finish()
  else:
    if message.text.isdigit():
      cur = conn.cursor()
      cur.execute(f"SELECT block FROM users WHERE user_id = {message.text}")
      result = cur.fetchall()
      conn.commit()
      if len(result) == 0:
        await message.answer('Такой пользователь не найден в базе данных.', reply_markup=kb)
        await state.finish()
      else:
        a = result[0]
        id = a[0]
        if id == 1:
          cur = conn.cursor()
          cur.execute(f"UPDATE users SET block = 0 WHERE user_id = {message.text}")
          conn.commit()
          await message.answer('Пользователь успешно разбанен.', reply_markup=kb)
          await state.finish()
          await bot.send_message(message.text, 'Вы были разблокированы администрацией.')
        else:
          await message.answer('Данный пользователь не получал бан.', reply_markup=kb)
          await state.finish()
    else:
      await message.answer('Ты вводишь буквы...\n\nВведи ID')

Теперь добавим статистику для нашего бота

Статистика

В отличии от других функций, эта будет самая простая.

Тут мы будем получать количество пользователей в боте, не активных, ни каких других, просто тех кто зашел хоть раз в бот

@dp.message_handler(content_types=['text'], text='Статистика')
async def hfandler(message: types.Message, state: FSMContext):
	cur = conn.cursor()
	cur.execute('''select * from users''')
  results = cur.fetchall()
  await message.answer(f'Людей которые когда либо заходили в бота: {len(results)}')

Теперь у вас есть еще и статистика для бота

Вот и всё!

В конце файла мы должны добавить две строчки

if __name__ == '__main__':
  executor.start_polling(dp, skip_updates=True)

Теперь просто запускаем и идем проверять!
Понимаю функционал не большой, не то что говорилось в названии, но я принимаю предложения что же добавить еще! В будущем ваще предложения я скорее всего реализую, и буду добавлять в эту статью, либо в новую!

Весь код выложен на GITHUB

Как обычно, я доступен в ТГ: @derkown

Спасибо за прочтение!

Комментарии (9)


  1. Metotron0
    03.01.2022 19:26
    +4

    Очень похоже на эпизодические комментарии к какому-то коду. Если уж это статья, то основой должен быть текст, описывающий идею, подходы к реализации, выбранную архитектуру и прочее иллюстрированное кодом. А сейчас в основе код, к которому местами приложены комментарии, которые можно заменить правильными именами функций. Если называть функцию не hanadler(), а addToBlacklist (или BlockList, кому как), не hfandler(), а unBlockUserById() (или короче, смотря насколько из контекста понятно, что в параметрах — id того, кого разблокируем, а то я в питоне не шарю, меня message.text несколько путает), не proce(), а blockUserById(), то комментарий "теперь сделаем добавление в ЧС" теряет смысл. И выходит, что вся статья состоит просто из кода. Оптимизируем подход — статья состоит из ссылки на гитхаб.

    За это и минусы, я полагаю.

    // По коду тоже вопросы: почему флаг блокировки назван id, а не isBlocked? Почему бы не объединить функции блокировки и разблокировки в одну? Разница ведь тооько в значении, которое передаётся полю block в базе.


    1. Metotron0
      04.01.2022 04:38
      +1

      Ещё хочется добавить: считать количество пользователей не через count(*), а через select * и len(results) — это уже за гранью.

      Я когда только с SQL познакомился, через 2-3 месяцев такое написал, а потом мне нужно было сходить в базу данных по ADSL. И тут я понял, зачем придуман count().

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

      В sqlite есть же rowid, зачем отдельно user_id заводить, который даже не auto_increment и не помечен уникальным? Или мне каких-то знаний не хватает?


    1. Borismedia
      06.01.2022 15:46

      Статья имеет название и полностью ко по мне соответствует ему. Всё достаточно просто и легко написано и откомменчено автором. Помню, когда я искал гайд по инету для быстрого старта с телеграмным ботом, то 9 из 10 тупо убили мое время, коды отказывались работать, а здесь что не так с кодом? (Кроме моментов, где можно оптимизировать). И вообще мне непонятно, почему такие умники заходят читать эти статьи ???? Вы не умеете создавать ботов на питоне?)) Или просто по жизни цель зайти и поминусовать чужих статей? ????


  1. Shaz
    03.01.2022 19:44
    +2

    А SQLite точно хороший выбор?


    1. bit314
      06.01.2022 15:47

      Думаю тут безразницы какая база используется, юзать можно ту которую ты знаешь и ее инструменты.


  1. avtozavodetz
    03.01.2022 20:08
    +6

    Не используйте форматные строки для динамического формирования запросов к БД! Это открытая дверь для SQL-инъекций!


    1. FridayJew
      05.01.2022 22:33
      -2

      Критикуешь - предлагай


  1. NeoLab
    04.01.2022 04:46
    +2

    После первой статьи уже стоило задуматься, почитать комментарии и на время затихнуть, изучая Python и SQL. Однако, увы, работы над ошибками нет.


  1. ErgoZru
    04.01.2022 20:37

    Напомнило поговорку: "никогда не сдавайся - позорься до конца"...

    Присоединяюсь к комментариям, что стоило прочитать комментарии к прошлому посту, сделать работу над ошибками... Но нет...