├── .gitignore ├── LICENSE.md ├── README.md ├── accounts.txt ├── browser.py ├── main.py ├── pages ├── base_page.py ├── gleam_io_page.py ├── twitter_login_page.py └── twitter_page.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Zakir 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 | ### About 2 | 3 | This is a bot that automates solving gleam.io giveaways that require actions with Twitter account 4 | 5 | ### Installation 6 | 7 | Install required packages 8 | ``` 9 | pip install -r requirements.txt 10 | ``` 11 | 12 | ### Usage 13 | 14 | 1. Change `REFERAL_LINK` constant in `main.py` 15 | 16 | 2. Add twitter accounts to `accounts.txt` 17 | They will be used during fulfilling giveaway requirements 18 | Each line represents one account - information is separated by comma: 19 | `username,password,phone number` 20 | Feel free to use current accounts which are defined in `accounts.txt` 21 | 2. Run main.py 22 | ``` 23 | python main.py 24 | ``` 25 | -------------------------------------------------------------------------------- /accounts.txt: -------------------------------------------------------------------------------- 1 | KayleeR89622644,liYz2SA9G0lq3if,6283838258320,12oujqwe123,tg_username 2 | LisaTur46104342,AJ2Tc8WKUljkoRq,6283141558013,1,1 3 | -------------------------------------------------------------------------------- /browser.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium import webdriver 4 | from selenium.common import exceptions 5 | from selenium.webdriver import chrome 6 | from selenium.webdriver.common.action_chains import ActionChains 7 | 8 | from webdriver_manager.chrome import ChromeDriverManager 9 | 10 | 11 | class Browser: 12 | 13 | # MARK: - Init 14 | 15 | def __init__(self, 16 | driver=webdriver.Chrome, 17 | options=chrome.options.Options(), 18 | caps=webdriver.DesiredCapabilities().CHROME): 19 | self.driver = driver 20 | self.options = options 21 | self.caps = caps 22 | 23 | # MARK: - Public methods 24 | 25 | def start(self): 26 | self.__configure() 27 | 28 | self.driver = webdriver.Chrome( 29 | ChromeDriverManager().install(), 30 | chrome_options=self.options, 31 | desired_capabilities=self.caps, 32 | ) 33 | 34 | def stop(self): 35 | if self.driver is None: 36 | return 37 | 38 | self.driver.quit() 39 | self.driver = None 40 | 41 | def get_url(self, url): 42 | self.driver.get(url) 43 | 44 | def switch_to_window(self, position): 45 | last_window = self.driver.window_handles[position] 46 | self.driver.switch_to.window(last_window) 47 | 48 | def action_on_first_interactable(self, elements, action_name='click'): 49 | for element in elements: 50 | try: 51 | actions = ActionChains(self.driver).move_to_element(element) 52 | getattr(actions, action_name)() 53 | actions.perform() 54 | except exceptions.ElementNotInteractableException: 55 | continue 56 | else: 57 | return True 58 | 59 | return False 60 | 61 | def wait_until_current_window_closed(self, timeout=3): 62 | while timeout > 0: 63 | try: 64 | # Raises error if window closed 65 | self.driver.current_window_handle 66 | except exceptions.NoSuchWindowException: 67 | # self.switch_to_window(1) # Maybe useless 68 | return True 69 | else: 70 | time.sleep(1) 71 | timeout -= 1 72 | 73 | return False 74 | 75 | # MARK: - Private methods 76 | 77 | def __configure(self): 78 | # Prevent selenium detection 79 | self.options.add_argument( 80 | '--disable-blink-features=AutomationControlled' 81 | ) 82 | 83 | # Run in silent mode 84 | # self.options.add_argument('--headless') 85 | 86 | # self.__disable_image_loading() 87 | self.__disable_full_load_waiting() 88 | 89 | def __disable_image_loading(self): 90 | prefs = { 91 | 'profile.default_content_settings': { 'images': 2 }, 92 | 'profile.managed_default_content_settings': { 'images': 2}, 93 | } 94 | self.options.experimental_options['prefs'] = prefs 95 | 96 | def __disable_full_load_waiting(self): 97 | self.caps['pageLoadStrategy'] = 'none' 98 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from browser import Browser 2 | from pages.gleam_io_page import GleamIoPage 3 | from pages.twitter_login_page import TwitterLoginPage 4 | from pages.twitter_page import TwitterPage 5 | 6 | 7 | # REFERAL_LINK = 'https://wn.nr/7G3qdE' 8 | REFERAL_LINK = 'https://gleam.io/zzxy5/fanaply-new-years-giveaway' 9 | ACCOUNTS_FILE_PATH = 'accounts.txt' 10 | 11 | 12 | def complete_twitter_task(user_info): 13 | try: 14 | browser = Browser() 15 | browser.start() 16 | 17 | browser.get_url(REFERAL_LINK) 18 | gleam_io_page = GleamIoPage(browser) 19 | 20 | gleam_io_page.open_twitter_login_page() 21 | 22 | twitter_login_page = TwitterLoginPage(browser) 23 | twitter_login_page.submit_credentials(user_info) 24 | 25 | # gleam_io_page.submit_details(user_info) 26 | 27 | gleam_io_page.click_follow_on_twitter_task() 28 | 29 | twitter_page = TwitterPage(browser) 30 | twitter_page.click_follow() 31 | 32 | gleam_io_page.mark_task_as_completed() 33 | 34 | browser.stop() 35 | except Exception as e: # TODO: Add specific exception 36 | browser.stop() 37 | print(e) 38 | 39 | if __name__ == '__main__': 40 | with open(ACCOUNTS_FILE_PATH, 'r') as file: 41 | for line in file: 42 | login, password, phone_number, bsc_address, tg_username = line.split(',') 43 | 44 | complete_twitter_task({ 45 | 'login': login, 46 | 'password': password, 47 | 'phone_number': phone_number, 48 | 'bsc_address': bsc_address, 49 | 'tg_username': tg_username, 50 | }) 51 | -------------------------------------------------------------------------------- /pages/base_page.py: -------------------------------------------------------------------------------- 1 | from selenium.common import exceptions 2 | from selenium.webdriver.support import expected_conditions as EC 3 | from selenium.webdriver.support.ui import WebDriverWait 4 | 5 | 6 | DEFAULT_WAIT_TIMEOUT = 5 7 | 8 | 9 | class BasePage: 10 | 11 | # MARK: - Init 12 | 13 | def __init__(self, browser): 14 | self.browser = browser 15 | 16 | # MARK: - Public methods 17 | 18 | def wait_until_found(self, *args, **kwargs): 19 | timeout = kwargs.get('timeout', DEFAULT_WAIT_TIMEOUT) 20 | 21 | try: 22 | element_present_condition = EC.presence_of_element_located(args) 23 | WebDriverWait(self.browser.driver, timeout) \ 24 | .until(element_present_condition) 25 | except exceptions.TimeoutException: 26 | print('Timeout waiting for element') 27 | -------------------------------------------------------------------------------- /pages/gleam_io_page.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.webdriver.common.by import By 4 | 5 | from pages.base_page import BasePage 6 | 7 | 8 | TWITTER_LOGIN_CSS_SELECTOR = "a.twitter-background.popup-window" 9 | FOLLOW_ON_TWITTER_CSS_SELECTOR = 'a.enter-link.twitter_follow-border' 10 | TWITTER_FOLLOW_BTN_CSS_SELECTOR = 'div.entry_details span.twitter-label' 11 | 12 | BSC_ADDRESS_INPUT_NAME = 'bsc_address' 13 | TG_USERNAME_INPUT_NAME = 'tg_username' 14 | # AGREE_TO_TERMS = 'i_have_read_the_terms_and_conditions' 15 | 16 | TASK_COMPLETED_LINK_SELECTOR = "a[ng-click='saveEntryDetails(entry_method)']" 17 | 18 | 19 | class GleamIoPage(BasePage): 20 | 21 | # MARK: - Init 22 | 23 | def __init__(self, browser): 24 | super().__init__(browser) 25 | 26 | # FIXME: Doesn't work 27 | self.wait_until_found(By.CSS_SELECTOR, TWITTER_LOGIN_CSS_SELECTOR) 28 | 29 | # MARK: - Public methods 30 | 31 | def open_twitter_login_page(self): 32 | links = self.__twitter_login_links() 33 | 34 | self.browser.action_on_first_interactable(links, action_name='click') 35 | 36 | def submit_details(self, user_info): 37 | bsc_address_input = self.__bsc_address_input() 38 | tg_username_input = self.__tg_username_input() 39 | 40 | bsc_address_input.send_keys(user_info['bsc_address']) 41 | tg_username_input.send_keys(user_info['tg_username']) 42 | 43 | def click_follow_on_twitter_task(self): 44 | links = self.__twitter_follow_links() 45 | self.browser.action_on_first_interactable(links, action_name='click') 46 | 47 | self.__wait_until_follow_btn_appeared() 48 | 49 | follow_btns = self.__follow_btns() 50 | self.browser.action_on_first_interactable( 51 | follow_btns, 52 | action_name='click' 53 | ) 54 | 55 | def mark_task_as_completed(self): 56 | self.browser.switch_to_window(0) 57 | 58 | task_completed_links = self.__check_if_task_completed_links() 59 | self.browser.action_on_first_interactable( 60 | task_completed_links, 61 | action_name='click' 62 | ) 63 | 64 | time.sleep(3) # FIXME 65 | 66 | # MARK: - Private methods 67 | 68 | def __wait_until_login_completed(self): 69 | self.wait_until_found(By.NAME, BSC_ADDRESS_INPUT_NAME) 70 | 71 | def __wait_until_follow_btn_appeared(self): 72 | self.wait_until_found(By.CSS_SELECTOR, TWITTER_FOLLOW_BTN_CSS_SELECTOR) 73 | 74 | def __twitter_login_links(self): 75 | return self.browser \ 76 | .driver \ 77 | .find_elements_by_css_selector(TWITTER_LOGIN_CSS_SELECTOR) 78 | 79 | def __twitter_follow_links(self): 80 | return self.browser \ 81 | .driver \ 82 | .find_elements_by_css_selector(FOLLOW_ON_TWITTER_CSS_SELECTOR) 83 | 84 | def __bsc_address_input(self): 85 | return self.browser \ 86 | .driver \ 87 | .find_element_by_name(BSC_ADDRESS_INPUT_NAME) 88 | 89 | def __tg_username_input(self): 90 | return self.browser \ 91 | .driver \ 92 | .find_element_by_name(TG_USERNAME_INPUT_NAME) 93 | 94 | def __follow_btns(self): 95 | return self.browser \ 96 | .driver \ 97 | .find_elements_by_css_selector(TWITTER_FOLLOW_BTN_CSS_SELECTOR) 98 | 99 | def __check_if_task_completed_links(self): 100 | return self.browser \ 101 | .driver \ 102 | .find_elements_by_css_selector(TASK_COMPLETED_LINK_SELECTOR) 103 | -------------------------------------------------------------------------------- /pages/twitter_login_page.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.common import exceptions 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.common.keys import Keys 6 | 7 | from pages.base_page import BasePage 8 | 9 | 10 | RESOURCE_NAME = 'gleam.io' 11 | USERNAME_OR_EMAIL_FIELD_ID = 'username_or_email' 12 | PASSWORD_FIELD_ID = 'password' 13 | PHONE_NUMBER_INPUT_ID = 'challenge_response' 14 | 15 | 16 | class TwitterLoginPage(BasePage): 17 | 18 | # MARK: - Init 19 | 20 | def __init__(self, browser): 21 | super().__init__(browser) 22 | 23 | self.browser.switch_to_window(-1) 24 | self.wait_until_found(By.ID, USERNAME_OR_EMAIL_FIELD_ID) 25 | 26 | # MARK: - Public methods 27 | 28 | def submit_credentials(self, credentials): 29 | username_field = self.__username_field() 30 | password_field = self.__password_field() 31 | 32 | username_field.send_keys(credentials['login']) 33 | password_field.send_keys(credentials['password']) 34 | 35 | password_field.send_keys(Keys.RETURN) 36 | 37 | if not self.browser.wait_until_current_window_closed(): 38 | self.__submit_phone_number(credentials['phone_number']) 39 | 40 | time.sleep(3) # FIXME 41 | self.browser.switch_to_window(0) 42 | 43 | # MARK: - Private methods 44 | 45 | def __submit_phone_number(self, phone_number): 46 | phone_number_field = self.__phone_number_field() 47 | 48 | phone_number_field.send_keys(phone_number) 49 | phone_number_field.send_keys(Keys.RETURN) 50 | 51 | def __username_field(self): 52 | return self.browser \ 53 | .driver \ 54 | .find_element_by_id(USERNAME_OR_EMAIL_FIELD_ID) 55 | 56 | def __password_field(self): 57 | return self.browser \ 58 | .driver \ 59 | .find_element_by_id(PASSWORD_FIELD_ID) 60 | 61 | def __phone_number_field(self): 62 | try: 63 | return self.browser \ 64 | .driver \ 65 | .find_element_by_id(PHONE_NUMBER_INPUT_ID) 66 | except exceptions.NoSuchElementException: 67 | return None 68 | -------------------------------------------------------------------------------- /pages/twitter_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from pages.base_page import BasePage 4 | 5 | 6 | FOLLOW_BTN_CSS_SELECTOR = "div[data-testid='confirmationSheetConfirm']" 7 | 8 | 9 | class TwitterPage(BasePage): 10 | 11 | # MARK: - Init 12 | 13 | def __init__(self, browser): 14 | super().__init__(browser) 15 | 16 | self.browser.switch_to_window(-1) 17 | self.wait_until_found( 18 | By.CSS_SELECTOR, FOLLOW_BTN_CSS_SELECTOR, 19 | timeout=10 20 | ) 21 | 22 | # MARK: - Public methods 23 | 24 | def click_follow(self): 25 | follow_btns = self.__follow_btns() 26 | 27 | self.browser.action_on_first_interactable( 28 | follow_btns, 29 | action_name='click' 30 | ) 31 | 32 | # MARK: - Private methods 33 | 34 | def __follow_btns(self): 35 | return self.browser \ 36 | .driver \ 37 | .find_elements_by_css_selector(FOLLOW_BTN_CSS_SELECTOR) 38 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | async-generator==1.10 2 | attrs==21.4.0 3 | certifi==2021.10.8 4 | cffi==1.15.0 5 | charset-normalizer==2.0.10 6 | colorama==0.4.4 7 | configparser==5.2.0 8 | crayons==0.4.0 9 | cryptography==36.0.1 10 | h11==0.12.0 11 | idna==3.3 12 | outcome==1.1.0 13 | pycparser==2.21 14 | pyOpenSSL==21.0.0 15 | requests==2.27.1 16 | selenium==4.1.0 17 | six==1.16.0 18 | sniffio==1.2.0 19 | sortedcontainers==2.4.0 20 | trio==0.19.0 21 | trio-websocket==0.9.2 22 | urllib3==1.26.7 23 | webdriver-manager==3.5.2 24 | wsproto==1.0.0 25 | --------------------------------------------------------------------------------