├── .gitignore ├── .template.env ├── LICENSE ├── README.md ├── bot ├── __init__.py ├── callbacks │ ├── __init__.py │ ├── admin.py │ ├── commands.py │ ├── core.py │ ├── mailing.py │ └── service.py ├── constants.py ├── handlers.py ├── models.py ├── tools_old.py └── utils │ ├── __init__.py │ └── checkers.py ├── images ├── example_en.png └── example_ru.png ├── main.py ├── requirements.txt └── settings.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | venv 3 | db.sqlite3 4 | .idea 5 | .DS_Store 6 | .env 7 | -------------------------------------------------------------------------------- /.template.env: -------------------------------------------------------------------------------- 1 | DEBUG= 2 | 3 | BOT_TOKEN= 4 | ADMINS= 5 | DEVELOPER= 6 | 7 | DB_NAME= 8 | DB_USER= 9 | DB_PASSWORD= 10 | DB_HOST= 11 | DB_PORT= 12 | 13 | SQLITE_DB_PATH= -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adrian Kalinin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CheckNicknameBot 2 | Telegram bot that checks if an username is available in social networks. 3 | 4 | # User Usage 5 | 6 | Everything is pretty simple – just send an username to the bot; or press the button if you want to check your own. 7 | 8 | ![Example](https://github.com/adreex/CheckNicknameBot/blob/master/resources/example_en.png) 9 | 10 | # Admin Usage 11 | 12 | There are some features for admins of the bot. Firt off all, you should enter `/admin` command. Then you can check statistics and restart the bot. 13 | 14 | ![Admin panel](https://github.com/adreex/CheckNicknameBot/blob/master/resources/readme_admin.png) 15 | 16 | One more great feature is the possibility to send a message to all the users. 17 | 18 | ![Mailing](https://github.com/adreex/CheckNicknameBot/blob/master/resources/readme_mailing.png) 19 | 20 | # Deployment 21 | 22 | ### Configurate `config.py`: 23 | 24 | Create a new Telegram Bot at t.me/BotFather and get the token of your bot, then put it as `token` variable. 25 | Then you can enter for `admins` some ids of users who can use the admins' commands. 26 | Fill your `host` (server's ip) and `port` (443, 80, 88 or 8443). 27 | 28 | ### Generate quick'n'dirty SSL certificate (in terminal): 29 | 30 | `openssl genrsa -out webhook_pkey.pem 2048` 31 | 32 | `openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem` 33 | 34 | Attention! When asked for "Common Name (e.g. server FQDN or YOUR name)" you should reply with the same value as your server's ip addres. 35 | 36 | ### Create virtual environment for Python and install all requiremetns (in terminal): 37 | 38 | `virtualenv venv --python=python3` 39 | 40 | `source venv/bin/activate` 41 | 42 | `pip install -r requiremetns.txt` 43 | 44 | Just enter `python main.py` in your terminal. 45 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | -------------------------------------------------------------------------------- /bot/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | from .service import error_callback 2 | 3 | from .commands import ( 4 | admin_command_callback, start_command_callback 5 | ) 6 | 7 | from .admin import ( 8 | statistics_callback, mailing_callback 9 | ) 10 | 11 | from .mailing import ( 12 | mailing_message_callback, preview_mailing_callback, 13 | cancel_mailing_callback, send_mailing_callback 14 | ) 15 | 16 | from .core import ( 17 | check_username_callback, check_my_username_callback, 18 | how_to_use_callback 19 | ) 20 | -------------------------------------------------------------------------------- /bot/callbacks/admin.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, ParseMode 2 | from telegram.ext import CallbackContext 3 | 4 | from peewee import fn 5 | 6 | from ..models import User 7 | from ..constants import Message, States 8 | 9 | 10 | def statistics_callback(update: Update, context: CallbackContext): 11 | total_users = User.select().count() 12 | active_users = User.select().where(User.active == True).count() 13 | total_requests = User.select(fn.sum(User.requests).alias('total')).dicts()[0].get('total') 14 | 15 | response = Message.statistics.format( 16 | total_users=total_users, 17 | active_users=active_users, 18 | total_requests=total_requests 19 | ) 20 | 21 | context.bot.edit_message_text( 22 | chat_id=update.effective_chat.id, 23 | message_id=update.effective_message.message_id, 24 | text=response, parse_mode=ParseMode.HTML 25 | ) 26 | 27 | 28 | def mailing_callback(update: Update, context: CallbackContext): 29 | context.bot.edit_message_text( 30 | chat_id=update.effective_chat.id, 31 | message_id=update.effective_message.message_id, 32 | text=Message.mailing 33 | ) 34 | 35 | return States.prepare_mailing 36 | -------------------------------------------------------------------------------- /bot/callbacks/commands.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, ParseMode 2 | from telegram.ext import CallbackContext 3 | 4 | from ..models import User 5 | from ..constants import Message, Keyboard 6 | 7 | 8 | def admin_command_callback(update: Update, context: CallbackContext): 9 | context.bot.send_message( 10 | chat_id=update.effective_chat.id, 11 | text=Message.admin, reply_markup=Keyboard.admin 12 | ) 13 | 14 | 15 | def start_command_callback(update: Update, context: CallbackContext): 16 | user, created = User.get_or_create(user_id=update.effective_user.id) 17 | 18 | if not user.active: 19 | user.active = True 20 | user.save() 21 | 22 | context.bot.send_message( 23 | chat_id=update.effective_chat.id, 24 | text=Message.start, parse_mode=ParseMode.HTML, 25 | reply_markup=Keyboard.main 26 | ) 27 | -------------------------------------------------------------------------------- /bot/callbacks/core.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, Bot, ParseMode 2 | from telegram.ext import CallbackContext 3 | 4 | import re 5 | 6 | from ..models import User 7 | from ..constants import Message, USERNAME_STATUSES 8 | from ..utils import checkers 9 | 10 | 11 | def __process_data(username): 12 | data = {social_media: None for social_media in checkers.keys()} 13 | statuses = {key + '_status': USERNAME_STATUSES[value]['emoji'] for key, value in data.items()} 14 | 15 | yield {**data, **statuses} 16 | 17 | for social_media in checkers.keys(): 18 | result = checkers[social_media](username) 19 | 20 | if result in [False, None]: 21 | data[social_media] = USERNAME_STATUSES[result]['text'] 22 | statuses[social_media + '_status'] = USERNAME_STATUSES[result]['emoji'] 23 | 24 | else: 25 | data[social_media] = USERNAME_STATUSES[True]['text'].format(result) 26 | statuses[social_media + '_status'] = USERNAME_STATUSES[True]['emoji'] 27 | 28 | yield {**data, **statuses} 29 | 30 | 31 | def __check_username(bot: Bot, username: str, user_id: int): 32 | checker = (__process_data(username)) 33 | 34 | data = next(checker) 35 | 36 | message = bot.send_message( 37 | chat_id=user_id, 38 | text=Message.result.format(username=username, bot_username=bot.username, **data), 39 | parse_mode=ParseMode.HTML, 40 | disable_web_page_preview=True 41 | ) 42 | 43 | for data in checker: 44 | message.edit_text( 45 | text=Message.result.format(username=username, bot_username=bot.username, **data), 46 | parse_mode=ParseMode.HTML, 47 | disable_web_page_preview=True 48 | ) 49 | 50 | 51 | def check_username_callback(update: Update, context: CallbackContext): 52 | username = update.message.text.strip(' ') 53 | 54 | if re.match('^[A-Za-z0-9_-]*$', username) and username.lower() != 'admin': 55 | query = User.update(requests=User.requests + 1).where(User.user_id == update.effective_user.id) 56 | query.execute() 57 | 58 | __check_username(context.bot, username, update.effective_user.id) 59 | 60 | else: 61 | context.bot.send_message( 62 | chat_id=update.effective_chat.id, 63 | text=Message.invalid_username 64 | ) 65 | 66 | 67 | def check_my_username_callback(update: Update, context: CallbackContext): 68 | if username := update.effective_user.username: 69 | __check_username(context.bot, username, update.effective_chat.id) 70 | 71 | else: 72 | context.bot.send_message( 73 | chat_id=update.effective_chat.id, 74 | text=Message.no_username 75 | ) 76 | 77 | 78 | def how_to_use_callback(update: Update, context: CallbackContext): 79 | context.bot.send_message( 80 | chat_id=update.effective_chat.id, 81 | text=Message.how_to_use 82 | ) 83 | -------------------------------------------------------------------------------- /bot/callbacks/mailing.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CallbackContext, ConversationHandler 2 | from telegram import ( 3 | Update, Message as TelegramMessage, 4 | TelegramError, Bot, PhotoSize, 5 | Animation, Video, ParseMode 6 | ) 7 | 8 | from threading import Thread 9 | import logging 10 | import time 11 | 12 | from ..constants import Message, Keyboard, States 13 | from ..models import User 14 | 15 | 16 | def __send_photo(bot: Bot, message: TelegramMessage, user_id: int): 17 | photo = PhotoSize( 18 | file_id=message.photo[0].file_id, 19 | file_unique_id=message.photo[0].file_unique_id, 20 | width=message.photo[0].width, 21 | height=message.photo[0].height, 22 | file_size=message.photo[0].file_size 23 | ) 24 | 25 | bot.send_photo( 26 | chat_id=user_id, 27 | photo=photo, 28 | caption=message.caption_html, 29 | parse_mode=ParseMode.HTML, 30 | reply_markup=message.reply_markup 31 | ) 32 | 33 | 34 | def __send_animation(bot: Bot, message: TelegramMessage, user_id: int): 35 | animation = Animation( 36 | file_id=message.animation.file_id, 37 | file_unique_id=message.animation.file_unique_id, 38 | width=message.animation.width, 39 | height=message.animation.height, 40 | duration=message.animation.file_size 41 | ) 42 | 43 | bot.send_animation( 44 | chat_id=user_id, 45 | animation=animation, 46 | caption=message.caption_html, 47 | parse_mode=ParseMode.HTML, 48 | reply_markup=message.reply_markup 49 | ) 50 | 51 | 52 | def __send_video(bot: Bot, message: TelegramMessage, user_id: int): 53 | video = Video( 54 | file_id=message.video.file_id, 55 | file_unique_id=message.video.file_unique_id, 56 | width=message.video.width, 57 | height=message.video.height, 58 | duration=message.video.file_size 59 | ) 60 | 61 | bot.send_video( 62 | chat_id=user_id, 63 | video=video, 64 | caption=message.caption_html, 65 | parse_mode=ParseMode.HTML, 66 | reply_markup=message.reply_markup 67 | ) 68 | 69 | 70 | def __send_message(bot: Bot, message: TelegramMessage, user_id: int): 71 | if message.photo: 72 | __send_photo(bot, message, user_id) 73 | 74 | if message.animation: 75 | __send_animation(bot, message, user_id) 76 | 77 | if message.video: 78 | __send_video(bot, message, user_id) 79 | 80 | elif message.text: 81 | bot.send_message( 82 | chat_id=user_id, 83 | text=message.text_html, 84 | parse_mode=ParseMode.HTML, 85 | reply_markup=message.reply_markup 86 | ) 87 | 88 | 89 | def __send_mailing(context: CallbackContext): 90 | message = context.user_data['mailing_message'] 91 | users = User.select().where(User.active == True) 92 | 93 | sent_count = 0 94 | 95 | for user in users: 96 | try: 97 | __send_message(context.bot, message, user.user_id) 98 | sent_count += 1 99 | time.sleep(1 / 20) 100 | 101 | logging.info(f'Mailing message sent to user {user.user_id} (total: {sent_count})') 102 | 103 | except TelegramError as ex: 104 | if ex.message == 'Forbidden: bot was blocked by the user': 105 | user.active = False 106 | user.save() 107 | 108 | logging.info(f'User {user.user_id} became inactive') 109 | 110 | else: 111 | logging.error(ex) 112 | 113 | except Exception as ex: 114 | logging.error(ex) 115 | 116 | return sent_count 117 | 118 | 119 | def mailing_message_callback(update: Update, context: CallbackContext): 120 | context.user_data['mailing_message'] = update.message 121 | 122 | context.bot.send_message( 123 | chat_id=update.effective_chat.id, 124 | text=Message.received_mailing, 125 | reply_markup=Keyboard.mailing 126 | ) 127 | 128 | return States.received_mailing 129 | 130 | 131 | def preview_mailing_callback(update: Update, context: CallbackContext): 132 | message = context.user_data['mailing_message'] 133 | __send_message(context.bot, message, update.effective_user.id) 134 | return States.received_mailing 135 | 136 | 137 | def cancel_mailing_callback(update: Update, context: CallbackContext): 138 | del context.user_data['mailing_message'] 139 | 140 | context.bot.send_message( 141 | chat_id=update.effective_chat.id, 142 | text=Message.mailing_canceled, 143 | reply_markup=Keyboard.main 144 | ) 145 | 146 | return ConversationHandler.END 147 | 148 | 149 | def __send_mailing_callback(update: Update, context: CallbackContext): 150 | context.bot.send_message( 151 | chat_id=update.effective_chat.id, 152 | text=Message.mailing_started, 153 | reply_markup=Keyboard.main 154 | ) 155 | 156 | logging.info('Mailing has started') 157 | sent_count = __send_mailing(context) 158 | 159 | context.bot.send_message( 160 | chat_id=update.effective_chat.id, 161 | text=Message.mailing_finished.format(sent_count=sent_count) 162 | ) 163 | 164 | logging.info('Mailing has finished') 165 | return ConversationHandler.END 166 | 167 | 168 | def send_mailing_callback(update: Update, context: CallbackContext): 169 | mailing_thread = Thread(target=__send_mailing_callback, args=(update, context)) 170 | mailing_thread.start() 171 | -------------------------------------------------------------------------------- /bot/callbacks/service.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, TelegramError, ParseMode 2 | from telegram.ext import CallbackContext 3 | import logging 4 | 5 | from settings import DEVELOPER 6 | 7 | from ..constants import Message 8 | 9 | 10 | # catch errors 11 | def error_callback(update: Update, context: CallbackContext): 12 | try: 13 | raise context.error 14 | 15 | except TelegramError as ex: 16 | logging.error(ex) 17 | 18 | context.bot.send_message( 19 | chat_id=DEVELOPER, 20 | text=Message.unexpected_error.format(error=context.error, update=update), 21 | parse_mode=ParseMode.HTML 22 | ) 23 | 24 | except Exception as ex: 25 | logging.error(ex) 26 | -------------------------------------------------------------------------------- /bot/constants.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardMarkup 2 | 3 | 4 | URLS = { 5 | 'instagram': 'https://www.instagram.com/{}/', 6 | 'twitter': 'https://twitter.com/{}/', 7 | 'vk': 'https://vk.com/{}/', 8 | 'facebook': 'https://www.facebook.com/{}/', 9 | 'github': 'https://github.com/{}/', 10 | 'telegram': 'https://t.me/{}/', 11 | 'tiktok': 'https://www.tiktok.com/@{}?' 12 | } 13 | 14 | USERNAME_STATUSES = { 15 | True: { 16 | 'emoji': '⛔', 17 | 'text': 'unavailable' 18 | }, 19 | 20 | False: { 21 | 'emoji': '✅', 22 | 'text': 'available' 23 | }, 24 | 25 | None: { 26 | 'emoji': '🔎', 27 | 'text': 'in progress' 28 | } 29 | } 30 | 31 | 32 | class States: 33 | prepare_mailing = 1 34 | received_mailing = 2 35 | 36 | 37 | class CallbackData: 38 | statistics = 'statistics' 39 | mailing = 'mailing' 40 | backup = 'backup' 41 | 42 | 43 | class ReplyButtons: 44 | check_my_username = '⚙️ Check my username' 45 | how_to_use = '💬 How to use?' 46 | 47 | send_mailing = 'Send' 48 | preview_mailing = 'Preview' 49 | cancel_mailing = 'Cancel' 50 | 51 | 52 | class Keyboard: 53 | main = ReplyKeyboardMarkup([ 54 | [ReplyButtons.check_my_username, ReplyButtons.how_to_use] 55 | ], resize_keyboard=True) 56 | 57 | admin = InlineKeyboardMarkup([ 58 | [InlineKeyboardButton('View statistics', callback_data=CallbackData.statistics)], 59 | [InlineKeyboardButton('Create broadcast', callback_data=CallbackData.mailing)] 60 | ]) 61 | 62 | mailing = ReplyKeyboardMarkup([ 63 | [ReplyButtons.send_mailing], 64 | [ReplyButtons.preview_mailing, ReplyButtons.cancel_mailing] 65 | ]) 66 | 67 | 68 | class Message: 69 | result = ( 70 | 'Information about username @{username}:\n\n' 71 | '{instagram_status} Instagram: {instagram}\n' 72 | '{twitter_status} Twitter: {twitter}\n' 73 | '{vk_status} Vkontakte: {vk}\n' 74 | '{facebook_status} Facebook: {facebook}\n' 75 | '{github_status} Github: {github}\n' 76 | '{tiktok_status} Tiktok: {tiktok}\n' 77 | '{telegram_status} Telegram: {telegram}\n\n' 78 | 'via @{bot_username}' 79 | ) 80 | 81 | start = ( 82 | 'Hey there 👋\n\n' 83 | "Just send me any username and I will check if the it's available on social medias. " 84 | 'Remember that usernames can contain only letters, numbers and underscores.\n\n' 85 | 'Have a good one!' 86 | ) 87 | 88 | how_to_use = ( 89 | "Just send me any username and I will check if the it's available on social medias. " 90 | 'Remember that usernames can contain only letters, numbers and underscores.\n\n' 91 | ) 92 | 93 | invalid_username = '💬 Usernames can only contain letters, numbers, underscores and dashes' 94 | 95 | no_username = "💬 Your profile doesn't have an username, you can set one in the settings" 96 | 97 | admin = 'Welcome to the admin panel!' 98 | 99 | statistics = ( 100 | "Bot's statistics:\n\n" 101 | 'Users in total: {total_users}\n' 102 | 'Active users: {active_users}\n' 103 | 'Number of requests: {total_requests}' 104 | ) 105 | 106 | mailing = 'Send a messaged for the broadcast' 107 | 108 | received_mailing = "The message has been received. What's next?" 109 | 110 | mailing_canceled = 'Broadcast has been cancelled' 111 | 112 | mailing_started = 'Broadcast had started' 113 | 114 | mailing_finished = ( 115 | 'Message has been sent:\n\n' 116 | 'Users that received the message: {sent_count}' 117 | ) 118 | 119 | unexpected_error = 'Telegram Error: {error}.\n\n{update}' 120 | -------------------------------------------------------------------------------- /bot/handlers.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler, MessageHandler, CallbackQueryHandler, ConversationHandler, Filters 2 | 3 | from settings import ADMINS 4 | 5 | from .constants import CallbackData, States, ReplyButtons 6 | from .callbacks import * 7 | 8 | 9 | # command handlers 10 | admin_handler = CommandHandler( 11 | command='admin', callback=admin_command_callback, 12 | filters=Filters.user(user_id=ADMINS) 13 | ) 14 | 15 | start_handler = CommandHandler( 16 | command='start', callback=start_command_callback 17 | ) 18 | 19 | # admin handlers 20 | statistics_handler = CallbackQueryHandler( 21 | pattern=CallbackData.statistics, 22 | callback=statistics_callback 23 | ) 24 | 25 | # mailing handlers 26 | mailing_conversation_handler = ConversationHandler( 27 | entry_points=[CallbackQueryHandler(pattern=CallbackData.mailing, callback=mailing_callback)], 28 | states={ 29 | States.prepare_mailing: [MessageHandler(callback=mailing_message_callback, filters=Filters.all)], 30 | States.received_mailing: [ 31 | MessageHandler(filters=Filters.text(ReplyButtons.preview_mailing), callback=preview_mailing_callback), 32 | MessageHandler(filters=Filters.text(ReplyButtons.cancel_mailing), callback=cancel_mailing_callback), 33 | MessageHandler(filters=Filters.text(ReplyButtons.send_mailing), callback=send_mailing_callback) 34 | ] 35 | }, 36 | fallbacks=[], 37 | run_async=True 38 | 39 | ) 40 | 41 | # core handlers 42 | check_my_username_handler = MessageHandler( 43 | filters=Filters.text(ReplyButtons.check_my_username), 44 | callback=check_my_username_callback, 45 | run_async=True 46 | ) 47 | 48 | how_to_use_handler = MessageHandler( 49 | filters=Filters.text(ReplyButtons.how_to_use), 50 | callback=how_to_use_callback 51 | ) 52 | 53 | check_username_handler = MessageHandler( 54 | filters=Filters.text, 55 | callback=check_username_callback, 56 | run_async=True 57 | ) 58 | -------------------------------------------------------------------------------- /bot/models.py: -------------------------------------------------------------------------------- 1 | from peewee import Model, IntegerField, BooleanField 2 | 3 | from settings import DEBUG 4 | 5 | if not DEBUG: 6 | from peewee import PostgresqlDatabase 7 | from settings import ( 8 | DB_NAME, DB_USER, DB_PASSWORD, 9 | DB_HOST, DB_PORT 10 | ) 11 | 12 | database = PostgresqlDatabase( 13 | database=DB_NAME, 14 | user=DB_USER, password=DB_PASSWORD, 15 | host=DB_HOST, port=DB_PORT 16 | ) 17 | 18 | else: 19 | from peewee import SqliteDatabase 20 | from settings import SQLITE_DB_PATH 21 | 22 | database = SqliteDatabase(SQLITE_DB_PATH) 23 | 24 | 25 | # base model for other models 26 | class BaseModel(Model): 27 | class Meta: 28 | database = database 29 | 30 | 31 | # model that represents user 32 | class User(BaseModel): 33 | user_id = IntegerField(primary_key=True, unique=True) 34 | requests = IntegerField(default=0) 35 | active = BooleanField(default=True) 36 | -------------------------------------------------------------------------------- /bot/tools_old.py: -------------------------------------------------------------------------------- 1 | from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton, PhotoSize 2 | from telegram.ext import CallbackContext 3 | from telegram.error import TelegramError, NetworkError 4 | 5 | from bot.utils.constants_old import username_statuses 6 | from .database import DataBase 7 | from bot.utils.checkers import checkers 8 | 9 | from functools import wraps 10 | import validators 11 | import logging 12 | import time 13 | import sys 14 | import os 15 | 16 | 17 | def send_action(action): 18 | def decorator(func): 19 | @wraps(func) 20 | def command_func(update: Update, context: CallbackContext, *args, **kwargs): 21 | context.bot.send_chat_action(chat_id=update.effective_message.chat_id, action=action) 22 | return func(update, context, *args, **kwargs) 23 | return command_func 24 | return decorator 25 | 26 | 27 | def strip(string): 28 | return string.strip() 29 | 30 | 31 | def stop_and_restart(): 32 | from main import updater 33 | updater.stop() 34 | logging.info('Bot has been stopped.') 35 | os.execl(sys.executable, sys.executable, *sys.argv) 36 | 37 | 38 | def send_mailing(bot, data: dict): 39 | successful, failed = 0, 0 40 | markup = None 41 | 42 | if data['photo'] or data['text']: 43 | yield True 44 | else: 45 | yield False 46 | 47 | if data['button']: 48 | text, url = map(strip, data['button'].split('-')) 49 | button = InlineKeyboardButton(text, url=url) 50 | markup = InlineKeyboardMarkup([[button]]) 51 | 52 | with DataBase() as db: 53 | 54 | for user_id in db.get_users(): 55 | try: 56 | if data['photo']: 57 | bot.send_photo( 58 | chat_id=user_id, photo=PhotoSize(*data['photo']), 59 | caption=data['text'], reply_markup=markup, 60 | parse_mode='HTML', disable_web_page_preview=True 61 | ) 62 | 63 | elif data['text']: 64 | bot.send_message( 65 | chat_id=user_id, text=data['text'], 66 | reply_markup=markup, parse_mode='HTML', 67 | disable_web_page_preview=True 68 | ) 69 | 70 | successful += 1 71 | time.sleep(1 / 20) 72 | 73 | except NetworkError as ex: 74 | if ex == 'Chat not found': 75 | db.del_user(user_id=user_id) 76 | failed += 1 77 | 78 | except TelegramError: 79 | db.del_user(user_id=user_id) 80 | failed += 1 81 | 82 | yield successful, failed 83 | 84 | 85 | def check_username(username: str): 86 | data = {social_media: None for social_media, _ in checkers.items()} 87 | yield data 88 | 89 | for social_media, checker in checkers.items(): 90 | data[social_media] = checker(username) 91 | yield data 92 | 93 | 94 | def process_data(data: dict, lang: str): 95 | text, status = None, None 96 | new_data = dict() 97 | 98 | for social_media, result in data.items(): 99 | if result is False: 100 | text = username_statuses[False][lang] 101 | status = '️✅' 102 | elif result is None: 103 | text = username_statuses[None][lang] 104 | status = '🔎' 105 | elif validators.url(result): 106 | text = username_statuses[True][lang].format(result) 107 | status = '⛔' 108 | 109 | new_data[social_media] = text 110 | new_data[social_media + '_status'] = status 111 | return new_data 112 | -------------------------------------------------------------------------------- /bot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .checkers import checkers 2 | -------------------------------------------------------------------------------- /bot/utils/checkers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from ..constants import URLS 4 | 5 | 6 | HEADERS = {'user-agent': ( 7 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' 8 | 'AppleWebKit/605.1.15 (KHTML, like Gecko) ' 9 | 'Version/14.1.1 Safari/605.1.15') 10 | } 11 | 12 | 13 | def _check_request(link: str): 14 | response = requests.get(link) 15 | if response.status_code == 200: 16 | return link 17 | return False 18 | 19 | 20 | def _check_instagram(username: str): 21 | link = URLS['instagram'].format(username) # TODO 22 | return _check_request(link) 23 | 24 | 25 | def _check_twitter(username: str): 26 | link = URLS['twitter'].format(username) # TODO 27 | return _check_request(link) 28 | 29 | 30 | def _check_vk(username: str): 31 | link = URLS['vk'].format(username) 32 | return _check_request(link) 33 | 34 | 35 | def _check_facebook(username: str): 36 | link = URLS['facebook'].format(username) 37 | return _check_request(link) 38 | 39 | 40 | def _check_github(username: str): 41 | link = URLS['github'].format(username) 42 | return _check_request(link) 43 | 44 | 45 | def _check_tiktok(username): 46 | link = URLS['tiktok'].format(username) 47 | if 'video-feed' in requests.get(link, headers=HEADERS).text: 48 | return link 49 | return False 50 | 51 | 52 | def _check_telegram(username): 53 | link = URLS['telegram'].format(username) 54 | if 'tgme_page_title' in requests.get(link, headers=HEADERS).text: 55 | return link 56 | return False 57 | 58 | 59 | checkers = { 60 | 'instagram': _check_instagram, 61 | 'twitter': _check_twitter, 62 | 'vk': _check_vk, 63 | 'facebook': _check_facebook, 64 | 'github': _check_github, 65 | 'tiktok': _check_tiktok, 66 | 'telegram': _check_telegram 67 | } 68 | -------------------------------------------------------------------------------- /images/example_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrian-kalinin/CheckNicknameBot/c5b38d400f7189d804aaf687cc7b12ee5187f0d7/images/example_en.png -------------------------------------------------------------------------------- /images/example_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adrian-kalinin/CheckNicknameBot/c5b38d400f7189d804aaf687cc7b12ee5187f0d7/images/example_ru.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import Updater 2 | import logging 3 | 4 | from settings import BOT_TOKEN 5 | 6 | from bot.models import database, User 7 | from bot.callbacks import error_callback 8 | 9 | from bot.handlers import ( 10 | start_handler, admin_handler, 11 | statistics_handler, mailing_conversation_handler, 12 | check_my_username_handler, how_to_use_handler, 13 | check_username_handler 14 | ) 15 | 16 | 17 | # set up logger 18 | logging.basicConfig( 19 | format='%(asctime)s – %(levelname)s – %(message)s', 20 | datefmt='%m/%d/%Y %I:%M:%S %p', 21 | level=logging.INFO 22 | ) 23 | 24 | 25 | # create updater 26 | updater = Updater(BOT_TOKEN) 27 | dispatcher = updater.dispatcher 28 | 29 | 30 | # bound handlers to dispatcher 31 | def bound_handlers(): 32 | # noinspection PyTypeChecker 33 | dispatcher.add_error_handler(error_callback) 34 | 35 | # command handlers 36 | dispatcher.add_handler(admin_handler) 37 | dispatcher.add_handler(start_handler) 38 | 39 | # admin handlers 40 | dispatcher.add_handler(statistics_handler) 41 | 42 | # mailing handlers 43 | dispatcher.add_handler(mailing_conversation_handler) 44 | 45 | # core handlers 46 | dispatcher.add_handler(check_my_username_handler) 47 | dispatcher.add_handler(how_to_use_handler) 48 | dispatcher.add_handler(check_username_handler) 49 | 50 | 51 | # set up database 52 | def configure_database(): 53 | database.connect() 54 | database.create_tables([User]) 55 | database.close() 56 | logging.info('Database has been configured') 57 | 58 | 59 | # set up webhook 60 | def configure_webhook(): 61 | pass 62 | 63 | 64 | def main(): 65 | # setting up application 66 | bound_handlers() 67 | configure_database() 68 | configure_webhook() 69 | 70 | # start bot 71 | updater.start_polling() 72 | logging.info('Bot has started') 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-telegram-bot==13.3 2 | peewee==3.14.1 3 | requests==2.32.0 4 | python-environ==0.4.54 5 | psycopg2==2.9.1 -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import environ 2 | 3 | 4 | env = environ.Env() 5 | 6 | READ_DOT_ENV_FILE = env.bool('READ_DOT_ENV_FILE', default=False) 7 | 8 | if READ_DOT_ENV_FILE: 9 | environ.Env.read_env() 10 | 11 | DEBUG = env.bool('DEBUG', False) 12 | 13 | BOT_TOKEN = env('BOT_TOKEN') 14 | ADMINS = list(map(int, env.list('ADMINS'))) 15 | DEVELOPER = env.int('DEVELOPER') 16 | 17 | if not DEBUG: 18 | DB_NAME = env('DB_NAME') 19 | DB_USER = env('DB_USER') 20 | DB_PASSWORD = env('DB_PASSWORD') 21 | DB_HOST = env('DB_HOST') 22 | DB_PORT = env.int('DB_PORT') 23 | 24 | else: 25 | SQLITE_DB_PATH = env('SQLITE_DB_PATH') 26 | --------------------------------------------------------------------------------