├── .env_sample ├── requirements.txt ├── config.py ├── Dockerfile ├── handlers └── handler.py ├── views └── messages.py ├── main.py ├── controllers ├── telegram_controller.py └── pardis_controller.py └── .gitignore /.env_sample: -------------------------------------------------------------------------------- 1 | token=229584564:ZAFz1lalAD9ZmZF956EnvEXAb0Q65CCRNpl -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | python-telegram-bot 3 | python-dotenv 4 | validator_collection 5 | fake_useragent -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from os import getenv 3 | 4 | load_dotenv() 5 | 6 | 7 | class Config: 8 | token = getenv("token") 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM python:3.8 3 | 4 | COPY . /app 5 | WORKDIR /app 6 | 7 | RUN pip3 install -U setuptools 8 | RUN apt-get install -y libssl-dev libffi-dev 9 | RUN apt-get install -y libxml2-dev libxslt1-dev zlib1g-dev 10 | RUN pip3 install -r requirements.txt 11 | 12 | CMD ["python3", "main.py"] -------------------------------------------------------------------------------- /handlers/handler.py: -------------------------------------------------------------------------------- 1 | from telegram import Update 2 | from telegram.ext import CallbackContext 3 | 4 | from controllers.telegram_controller import BotController 5 | 6 | 7 | def start_and_help(update: Update, context: CallbackContext) -> int: 8 | return BotController(update, context).start() 9 | 10 | def go_to_class_now(update: Update, context: CallbackContext) -> int: 11 | return BotController(update, context).go_to_the_class() 12 | 13 | def get_user_credentials_intro(update: Update, context: CallbackContext) -> int: 14 | return BotController(update, context).get_user_credentials_intro() 15 | 16 | def get_user_credentials(update: Update, context: CallbackContext) -> int: 17 | return BotController(update, context).get_user_credentials() 18 | 19 | def take_screenshot(update: Update, context: CallbackContext) -> int: 20 | return BotController(update, context).take_screenshot() 21 | 22 | def cancel(update: Update, context: CallbackContext) -> int: 23 | return BotController(update, context).cancel() -------------------------------------------------------------------------------- /views/messages.py: -------------------------------------------------------------------------------- 1 | from os import stat 2 | from random import choice 3 | import re 4 | 5 | from telegram.ext import messagehandler 6 | 7 | class View: 8 | @staticmethod 9 | def welcome(): 10 | message = "سلام. در بتا هستیم. باگ ها طبیعین." 11 | return message 12 | 13 | @staticmethod 14 | def how_to_send_credentials(): 15 | message = "در خط اول یوزرنیم یا همون کدملیتو بفرست. تو خط دوم هم پسووردو بده بیاد :)" 16 | return message 17 | 18 | @staticmethod 19 | def signup_failed(): 20 | message = "فرمت یوزرنیم پسووردتون صحیح نیست." 21 | return message 22 | 23 | @staticmethod 24 | def signup_ok(): 25 | messages = ["اوکی!", "حله!", "عالی!"] 26 | message = choice(messages) 27 | return message 28 | 29 | @staticmethod 30 | def credentials_are_required(): 31 | message = "یوزرنیم پسووردتو نداریم. ساین آپ کن." 32 | return message 33 | 34 | @staticmethod 35 | def cancel(): 36 | message = "کنسل گردید." 37 | return message -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import ( 2 | Updater, 3 | CommandHandler, 4 | MessageHandler, 5 | Filters, 6 | ConversationHandler 7 | ) 8 | 9 | from handlers.handler import * 10 | from config import Config 11 | 12 | GET_CREDENTIALS = 1 13 | 14 | def main() -> None: 15 | updater = Updater(Config.token) 16 | dispatcher = updater.dispatcher 17 | 18 | dispatcher.add_handler(CommandHandler(["start", "help"], start_and_help)) 19 | dispatcher.add_handler(CommandHandler(["screenshot", "ss", "status"], take_screenshot)) 20 | dispatcher.add_handler(CommandHandler(["go", "got_to_my_class"], go_to_class_now)) 21 | signup_converstation = ConversationHandler( 22 | entry_points=[CommandHandler(["signup"], get_user_credentials_intro)], 23 | states={ 24 | GET_CREDENTIALS: [ 25 | MessageHandler(Filters.text, get_user_credentials) 26 | ] 27 | }, 28 | fallbacks=[CommandHandler("cancel", cancel)] 29 | ) 30 | dispatcher.add_handler(signup_converstation) 31 | 32 | 33 | updater.start_polling() 34 | updater.idle() 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /controllers/telegram_controller.py: -------------------------------------------------------------------------------- 1 | from views.messages import View 2 | from telegram.ext import ConversationHandler 3 | from controllers.pardis_controller import PardisSelenium 4 | import threading 5 | 6 | 7 | class BotController: 8 | def __init__(self, update, context): 9 | self.update = update 10 | self.context = context 11 | 12 | def get_user_credentials_intro(self): 13 | get_user_credentials = 1 14 | self.update.message.reply_text(View.how_to_send_credentials()) 15 | return get_user_credentials 16 | 17 | def get_user_credentials(self): 18 | text = self.update.message.text 19 | text_splitted = text.split("\n") 20 | if len(text_splitted) != 2: 21 | self.update.message.reply_text(View.signup_failed()) 22 | return False 23 | username = text_splitted[0] 24 | password = text_splitted[1] 25 | self.context.user_data["user_credentials"] = { 26 | "username": username, 27 | "password": password, 28 | } 29 | self.update.message.reply_text(View.signup_ok()) 30 | return ConversationHandler.END 31 | 32 | def start(self): 33 | welcome_message = View.welcome() 34 | self.update.message.reply_text(welcome_message) 35 | return True 36 | 37 | def go_to_the_class(self): 38 | if "user_credentials" not in self.context.user_data: 39 | self.update.message.reply_text(View.credentials_are_required()) 40 | return False 41 | if "driver" in self.context.user_data: 42 | self.context.user_data["driver"].close() 43 | self.context.user_data.pop("driver") 44 | 45 | pardis = PardisSelenium() 46 | self.context.user_data["driver"] = pardis.driver 47 | go_to_class = threading.Thread( 48 | target=pardis.go_to_class, 49 | args=(self.context.user_data["user_credentials"],), 50 | ) 51 | go_to_class.start() 52 | 53 | def take_screenshot(self): 54 | if "driver" in self.context.user_data: 55 | self.update.message.reply_text(self.context.user_data["driver"].current_url) 56 | self.context.user_data["driver"].save_screenshot("screenshot.png") 57 | self.update.message.reply_photo(open("screenshot.png", "rb")) 58 | return True 59 | return False 60 | 61 | def cancel(self): 62 | self.update.message.reply_text(View.cancel()) 63 | return ConversationHandler.END -------------------------------------------------------------------------------- /controllers/pardis_controller.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from validator_collection import is_not_empty 3 | from fake_useragent import UserAgent 4 | from time import sleep 5 | from selenium.webdriver.chrome.options import Options 6 | 7 | 8 | class PardisSelenium: 9 | def __init__(self): 10 | options = Options() 11 | options.add_argument('--headless') 12 | options.add_argument('--disable-gpu') 13 | options.add_argument("window-size=1920,1080") 14 | self.driver = webdriver.Chrome(chrome_options=options) 15 | self.domain = "https://www.helli4.ir/" 16 | self.login_url = self.domain + "portal/user/" 17 | self.classes_url = self.domain + "pds/vclasses.php" 18 | 19 | def log_in(self, user_credentials): 20 | print("Logging in...") 21 | self.driver.get(self.login_url) 22 | print("Loading login page...") 23 | login_form = self.driver.find_elements_by_id("user-login") 24 | if not is_not_empty(login_form): 25 | print("Already logged in.") 26 | return True 27 | login_form = login_form[0] 28 | 29 | print("Putting in username...") 30 | username = login_form.find_element_by_id("edit-name") 31 | username.send_keys(user_credentials["username"]) 32 | print("Putting in password...") 33 | password = login_form.find_element_by_id("edit-pass") 34 | password.send_keys(user_credentials["password"]) 35 | print("Submitting login form...") 36 | login_button = login_form.find_element_by_id("edit-submit") 37 | login_button.click() 38 | return True 39 | 40 | def go_to_adobe_connect(self): 41 | print("Changing user agent and platform so fucking adobe connect doesn't stop me from loggin in...") 42 | self.driver.execute_cdp_cmd('Network.setUserAgentOverride', {"userAgent": UserAgent().chrome, "platform": "Android"}) 43 | print("Going to classes page...") 44 | self.driver.get(self.classes_url) 45 | print("Clicking on user class...") 46 | user_class_button = self.driver.find_elements_by_class_name("form-control")[0] 47 | user_class_button.click() 48 | sleep(5) 49 | while True: 50 | self.driver.refresh() 51 | sleep(10) 52 | print("Clicking on open in browser button...") 53 | try: 54 | open_in_browser_button = self.driver.find_elements_by_class_name("button-content")[0] 55 | open_in_browser_button.click() 56 | break 57 | except: 58 | pass 59 | 60 | return True 61 | 62 | def go_to_class(self, user_credentials): 63 | self.log_in(user_credentials) 64 | self.go_to_adobe_connect() 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | # End of https://www.toptal.com/developers/gitignore/api/python 146 | --------------------------------------------------------------------------------