├── source
├── .env
├── __init__.py
├── helper
│ ├── __init__.py
│ └── complement.py
├── interface
│ ├── __init__.py
│ ├── extension
│ │ ├── __init__.py
│ │ ├── extension_manager.py
│ │ └── plugin_manager.py
│ ├── interface.py
│ └── driver_interface.py
├── service
│ ├── __init__.py
│ ├── extension
│ │ ├── __init__.py
│ │ ├── download_extension_firefox.py
│ │ └── download_extension_chrome.py
│ ├── user_agent_browser.py
│ ├── driver_manager.py
│ ├── driver
│ │ ├── configuration_firefox.py
│ │ └── configuration_chrome.py
│ └── driver_action.py
├── controller
│ ├── __init__.py
│ ├── extension
│ │ ├── __init__.py
│ │ ├── captchasolver.py
│ │ └── metamask.py
│ ├── runner.py
│ ├── extension_manager.py
│ └── web_driver.py
├── .pylintrc
├── .idea
│ ├── vcs.xml
│ ├── misc.xml
│ ├── .gitignore
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── modules.xml
│ └── source.iml
├── main.py
└── requirements.txt
├── .pylintrc
├── .idea
├── vcs.xml
├── .gitignore
├── misc.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
└── selenium-docker.iml
├── docker
├── python
│ └── Dockerfile
└── single
│ └── Dockerfile
├── .env.example
├── .github
└── workflows
│ ├── pylint.yml
│ └── codeql.yml
├── docker-compose.yml
├── README.md
├── .gitignore
├── .editorconfig
└── .dockerignore
/source/.env:
--------------------------------------------------------------------------------
1 | ../.env
--------------------------------------------------------------------------------
/source/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/helper/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/interface/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/service/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/controller/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/interface/extension/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/controller/extension/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/service/extension/__init__.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 |
--------------------------------------------------------------------------------
/source/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.join(os.path.dirname(find_pylintrc())))"
3 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.join(os.path.dirname(find_pylintrc()), 'source'))"
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/source/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/source/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/source/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/source/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/source/main.py:
--------------------------------------------------------------------------------
1 | """
2 | Main Run file for the project.
3 | """
4 | import os
5 |
6 | from controller.runner import Runner
7 |
8 | if __name__ == "__main__":
9 | root_path = os.path.dirname(os.path.abspath(__file__))
10 | Runner(root_path)
11 |
--------------------------------------------------------------------------------
/source/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/source/.idea/source.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/selenium-docker.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docker/python/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10
2 |
3 | ENV LANG C.UTF-8
4 | ENV LC_ALL C.UTF-8
5 | ENV PYTHONUNBUFFERED=1
6 | ENV APP_HOME /usr/src/app
7 |
8 | WORKDIR /$APP_HOME
9 |
10 | COPY ./source/ $APP_HOME/
11 | COPY ./.env $APP_HOME/
12 |
13 | RUN apt-get -y update
14 |
15 | RUN pip install --upgrade pip
16 |
17 | RUN pip install -r requirements.txt
18 |
19 | CMD tail -f /dev/null
20 | # CMD python3 main.py
--------------------------------------------------------------------------------
/source/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.12
6 | cryptography==37.0.2
7 | h11==0.13.0
8 | idna==3.3
9 | outcome==1.1.0
10 | pycparser==2.21
11 | pyOpenSSL==22.0.0
12 | PySocks==1.7.1
13 | python-dotenv==0.20.0
14 | PyVirtualDisplay==3.0
15 | requests==2.27.1
16 | selenium==4.1.5
17 | sniffio==1.2.0
18 | sortedcontainers==2.4.0
19 | trio==0.20.0
20 | trio-websocket==0.9.2
21 | urllib3==1.26.9
22 | webdriver-manager==3.5.4
23 | wsproto==1.1.0
24 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Choose an environment (local, docker, remote)
2 | ENVIRONMENT=local
3 |
4 | # Path File of the assets/drivers/etc..
5 | PATH_ASSETS=assets
6 |
7 | # Choose a Browser (chrome, firefox)
8 | BROWSER=chrome
9 | # Available extensions in Chrome Store
10 | EXTENSIONS_CHROME=["metamask"]
11 | # Available extensions in Firefox Store
12 | #EXTENSIONS_FIREFOX=["metamask"]
13 |
14 | # Posible Routes to ENVIRONMENT=remote
15 | REMOTE_URL=http://selenium-hub:4444/wd/hub
16 | #REMOTE_URL=http://0.0.0.0:4444/wd/hub
17 | #REMOTE_URL=http://localhost:4444/wd/hub
18 |
19 | # If you active Metamask extension, put your credential here.
20 | METAMASK_AUTH=["SECRET", "A B C D E F G H I J K L"]
21 |
--------------------------------------------------------------------------------
/.github/workflows/pylint.yml:
--------------------------------------------------------------------------------
1 | name: Pylint
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | python-version: ["3.10"]
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Set up Python ${{ matrix.python-version }}
14 | uses: actions/setup-python@v3
15 | with:
16 | python-version: ${{ matrix.python-version }}
17 | - name: Install dependencies
18 | run: |
19 | python -m pip install --upgrade pip
20 | pip install pylint
21 | pip install -r source/requirements.txt
22 | - name: Analysing the code with pylint
23 | run: |
24 | pylint $(git ls-files '*.py')
25 |
--------------------------------------------------------------------------------
/source/service/user_agent_browser.py:
--------------------------------------------------------------------------------
1 | """
2 | User Agent Browser Service
3 | """
4 | from helper.complement import Complement
5 |
6 |
7 | class UserAgentBrowser:
8 | """ UserAgentBrowser class """
9 | path_file = None
10 |
11 | def __init__(self, path_asset, browser, driver):
12 | self.path_file = f"{path_asset}/user_agent_{browser}.txt"
13 | self.driver = driver
14 |
15 | def _get_user_agent(self):
16 | """ Get the user agent """
17 | command = "return navigator.userAgent"
18 | data_user_agent = self.driver.execute_script(command)
19 | Complement.write_file(self.path_file, data_user_agent)
20 |
21 | def exist_user_agent(self):
22 | """ Check if the file exist """
23 | return Complement.check_file_exist(self.path_file)
24 |
25 | def data_user_agent(self):
26 | """ Return the data of the file """
27 | if self.exist_user_agent():
28 | return Complement.read_file(self.path_file)
29 |
30 | self._get_user_agent()
31 | return self.data_user_agent()
32 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`
2 | # Add the `-d` flag at the end for detached execution
3 | # To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down`
4 | version: "3"
5 | services:
6 | chrome:
7 | image: selenium/node-chrome:4.1.4-20220427
8 | shm_size: 2gb
9 | depends_on:
10 | - selenium-hub
11 | environment:
12 | - SE_EVENT_BUS_HOST=selenium-hub
13 | - SE_EVENT_BUS_PUBLISH_PORT=4442
14 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
15 |
16 | # edge:
17 | # image: selenium/node-edge:4.1.4-20220427
18 | # shm_size: 2gb
19 | # depends_on:
20 | # - selenium-hub
21 | # environment:
22 | # - SE_EVENT_BUS_HOST=selenium-hub
23 | # - SE_EVENT_BUS_PUBLISH_PORT=4442
24 | # - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
25 |
26 | firefox:
27 | image: selenium/node-firefox:4.1.4-20220427
28 | shm_size: 2gb
29 | depends_on:
30 | - selenium-hub
31 | environment:
32 | - SE_EVENT_BUS_HOST=selenium-hub
33 | - SE_EVENT_BUS_PUBLISH_PORT=4442
34 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
35 | volumes:
36 | - ./source/assets/extensions/firefox:/usr/src/app/assets/extensions/firefox
37 |
38 | selenium-hub:
39 | image: selenium/hub:4.1.4-20220427
40 | container_name: selenium-hub
41 | ports:
42 | - "4442:4442"
43 | - "4443:4443"
44 | - "4444:4444"
45 |
46 | app:
47 | build:
48 | context: .
49 | dockerfile: ./docker/python/Dockerfile
50 | env_file:
51 | - .env
52 | ports:
53 | - "4000:4000"
54 | volumes:
55 | - .env:/usr/src/app/.env
56 | - ./source:/usr/src/app
57 | privileged: true
--------------------------------------------------------------------------------
/source/helper/complement.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper module - Complement.
3 | """
4 | import os
5 | import shutil
6 |
7 |
8 | class Complement:
9 | """ Complement class. """
10 |
11 | @staticmethod
12 | def browser_is_chrome(browser):
13 | """ Check if browser is chrome. """
14 | return browser == "chrome"
15 |
16 | @staticmethod
17 | def browser_is_firefox(browser):
18 | """ Check if browser is firefox. """
19 | return browser == "firefox"
20 |
21 | @staticmethod
22 | def check_file_exist(path):
23 | """ Check if file exist. """
24 | return os.path.isfile(path)
25 |
26 | @staticmethod
27 | def make_folder(path):
28 | """ Create folder. """
29 | if not os.path.exists(path):
30 | os.makedirs(path)
31 |
32 | @staticmethod
33 | def move_file(src, dest):
34 | """ Move file. """
35 | shutil.move(src, dest)
36 |
37 | @staticmethod
38 | def write_file(path, content, mode="w", encoding="utf8"):
39 | """ Write file. """
40 | if mode == 'wb':
41 | # pylint: skip-file
42 | with open(path, mode) as file:
43 | file.write(content)
44 | else:
45 | with open(path, mode, encoding=encoding) as file:
46 | file.write(content)
47 |
48 | @staticmethod
49 | def read_file(path, encoding="utf8"):
50 | """ Read file. """
51 | with open(path, 'r', encoding=encoding) as file:
52 | return file.read()
53 |
54 | @staticmethod
55 | def convertENVArray(env_array):
56 | """ Convert ENV[] to Array """
57 | return env_array.replace('[', '').replace(']', '') \
58 | .replace('"', '') \
59 | .replace(', ', ',').replace(' ,', ',') \
60 | .split(',')
61 |
--------------------------------------------------------------------------------
/source/interface/interface.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | """
3 | Interface.
4 | """
5 |
6 |
7 | def abstractfunc(func):
8 | """ Decorator for abstract functions. """
9 | func.__isabstract__ = True
10 | return func
11 |
12 |
13 | class Interface(type):
14 | """ Metaclass for interfaces. """
15 |
16 | def __init__(self, name, bases, namespace):
17 | """ Check that all abstract methods are implemented. """
18 | for base in bases:
19 | must_implement = getattr(base, "abstract_methods", [])
20 | class_methods = getattr(self, "all_methods", [])
21 | for method in must_implement:
22 | if method not in class_methods:
23 | err_str = """Can't create abstract class {name}!
24 | {name} must implement abstract method {method}
25 | of class {base_class}!""".format(
26 | name=name, method=method, base_class=base.__name__
27 | )
28 | raise TypeError(err_str)
29 |
30 | def __new__(metaclass, name, bases, namespace):
31 | interface_methods = Interface._get_abstract_methods(namespace)
32 | namespace["abstract_methods"] = interface_methods
33 | namespace["all_methods"] = Interface._get_all_methods(namespace)
34 | cls = super().__new__(metaclass, name, bases, namespace)
35 | return cls
36 |
37 | def _get_abstract_methods(namespace):
38 | """ Return a list of abstract methods. """
39 | return [
40 | name
41 | for name, val in namespace.items()
42 | if callable(val) and getattr(val, "__isabstract__", False)
43 | ]
44 |
45 | def _get_all_methods(namespace):
46 | """ Return a list of all methods. """
47 | return [name for name, val in namespace.items() if callable(val)]
48 |
--------------------------------------------------------------------------------
/source/controller/runner.py:
--------------------------------------------------------------------------------
1 | """
2 | Runner for the controller.
3 | """
4 | import os
5 |
6 | from dotenv import load_dotenv
7 | from pyvirtualdisplay import Display
8 |
9 | from controller.web_driver import WebDriver
10 |
11 |
12 | class Runner:
13 | """ Runner class """
14 | driver = None
15 | webdriver = None
16 | display = None
17 |
18 | root_path = None
19 | path_asset = None
20 |
21 | browser = 'chrome'
22 | environment = 'local'
23 |
24 | def __init__(self, root_path):
25 | self.root_path = root_path
26 | self.run()
27 |
28 | def run(self):
29 | """ Run """
30 | self.configuration()
31 | self.configure_browser()
32 | self.test()
33 |
34 | def configuration(self):
35 | """ Configuration """
36 | load_dotenv()
37 | self.path_asset = os.getenv('PATH_ASSETS') or 'assets'
38 | self.path_asset = self.root_path + '/' + self.path_asset
39 | self.environment = os.getenv('ENVIRONMENT') or 'local'
40 | self.browser = os.getenv('BROWSER') or 'chrome'
41 |
42 | def configure_browser(self):
43 | """ Configure browser """
44 | if self.environment == 'docker':
45 | self.start_display()
46 | self.start_webdriver()
47 |
48 | def start_webdriver(self):
49 | """ Start webdriver """
50 | self.webdriver = WebDriver(self.path_asset, self.browser, self.environment)
51 | self.driver = self.webdriver.get_driver()
52 |
53 | def start_display(self):
54 | """ Start display """
55 | self.display = Display(size=(800, 600))
56 | self.display.start()
57 |
58 | def stop_display(self):
59 | """ Stop display """
60 | self.display.stop()
61 |
62 | def test(self):
63 | """ Test """
64 | self.webdriver.start()
65 | if self.environment == 'docker':
66 | self.stop_display()
67 |
--------------------------------------------------------------------------------
/source/interface/extension/extension_manager.py:
--------------------------------------------------------------------------------
1 | """
2 | Extension Manager
3 | """
4 | from abc import abstractmethod
5 |
6 | import requests
7 |
8 | from helper.complement import Complement
9 | from interface.interface import Interface
10 |
11 |
12 | class ExtensionManager(metaclass=Interface):
13 | """ Extension Manager Interface """
14 | path_file = None
15 | url_extension_firefox = None
16 |
17 | def __init__(self):
18 | self.driver = None
19 |
20 | # @abstractmethod
21 | def set_driver(self, driver):
22 | """ Set driver """
23 | self.driver = driver
24 |
25 | @abstractmethod
26 | def _get_info(self, url_base_extension):
27 | """ Get info extension """
28 | data_main = url_base_extension.split("/")
29 | file_name = data_main[-1]
30 | return {"file_name": file_name}
31 |
32 | @abstractmethod
33 | def generate_extension(self, url):
34 | """ Generate extension """
35 | # info_extension = self._get_info(url)
36 | # path_file = self._get_path_extension(url, info_extension)
37 | # self._download_extension(url, path_file)
38 | # return path_file
39 |
40 | # @abstractmethod
41 | def _get_path_extension(self, url_chrome_extension, info_extension=None):
42 | """ Get path extension """
43 | if info_extension is None:
44 | info_extension = self._get_info(url_chrome_extension)
45 | Complement.make_folder(self.path_file)
46 | path_file = f"{self.path_file}/{info_extension['file_name']}"
47 | return path_file
48 |
49 | # @abstractmethod
50 | def set_extension_path(self, url_base_extension):
51 | """ Set extension path """
52 | path_file = self._get_path_extension(url_base_extension)
53 | return path_file
54 |
55 | # @abstractmethod
56 | def exist_extension_by_url(self, url_base_extension):
57 | """ Exist extension by url """
58 | path_extension = self.set_extension_path(url_base_extension)
59 | return {
60 | "path_file": path_extension,
61 | "exist": Complement.check_file_exist(path_extension),
62 | }
63 |
64 | # @abstractmethod
65 | @staticmethod
66 | def _download_extension(url, path_file):
67 | """ Download extension """
68 | request_uri = requests.get(url)
69 | content = request_uri.content
70 | Complement.write_file(path_file, content, 'wb')
71 |
--------------------------------------------------------------------------------
/source/service/extension/download_extension_firefox.py:
--------------------------------------------------------------------------------
1 | """
2 | Download Extension - Firefox
3 | """
4 | import requests
5 |
6 | from interface.extension.extension_manager import ExtensionManager
7 |
8 |
9 | def get_main_url(url_extension, name_extension):
10 | """ Get Main Url """
11 | response = requests.get(url_extension)
12 | response_json = response.json()
13 | url_extension = ""
14 | for item in response_json["results"]:
15 | if item["slug"] == name_extension:
16 | url_extension = item["current_version"]["file"]["url"]
17 | break
18 | return url_extension
19 |
20 |
21 | class DownloadExtensionFirefox(ExtensionManager):
22 | """ Download Extensions Firefox """
23 |
24 | path_file = "extensions/firefox"
25 | # Firefox
26 | url_extension_firefox = (
27 | "https://addons.mozilla.org/api/v5/addons/search/"
28 | "?app=firefox&author={user_ids}"
29 | "&exclude_addons={name_extension}"
30 | "&page=1&page_size=10&sort=hotness"
31 | "&type=extension&lang=en-US"
32 | )
33 |
34 | def __init__(self, path_assets):
35 | super().__init__()
36 | self.path_file = f"{path_assets}/{self.path_file}"
37 |
38 | def get_base_url(self, object_url):
39 | """ Get Base Url """
40 | name_extension = object_url["collection_url"].split("/")[-1]
41 | user_ids = "%2C".join(str(x) for x in object_url["user_id"])
42 | url_extension = self.url_extension_firefox.format(
43 | user_ids=user_ids, name_extension=name_extension
44 | )
45 | return url_extension
46 |
47 | def get_url_extension(self, object_url):
48 | """ Get Url Extensions"""
49 | url_extension = self.get_base_url(object_url)
50 | name_real_extension = object_url["url"].split("/")[-1]
51 | main_url = get_main_url(url_extension, name_real_extension)
52 | return main_url
53 |
54 | # Override
55 | def _get_info(self, url_base_extension):
56 | """ Get Info """
57 | data_main = url_base_extension.split("/")
58 | file_name = data_main[-1]
59 | return {"file_name": file_name}
60 |
61 | def generate_extension(self, url):
62 | """ Generate extension """
63 | info_extension = self._get_info(url)
64 | path_file = self._get_path_extension(url, info_extension)
65 | self._download_extension(url, path_file)
66 | return path_file
67 |
--------------------------------------------------------------------------------
/source/controller/extension_manager.py:
--------------------------------------------------------------------------------
1 | """
2 | Extension Manager for the controller.
3 | """
4 | import os
5 |
6 | from controller.extension.captchasolver import CaptchaSolver
7 | from controller.extension.metamask import MetaMask
8 | from helper.complement import Complement
9 |
10 |
11 | class ExtensionManager:
12 | """ Extension Manager for the controller. """
13 | path_assets = None
14 | browser = None
15 | driver_action = None
16 | driver = None
17 | wallet = None
18 | captcha = None
19 | extensions = []
20 |
21 | def __init__(self, path_assets, browser, driver_action=None, driver=None):
22 | self.path_assets = path_assets
23 | self.browser = browser
24 | self.driver_action = driver_action
25 | self.driver = driver
26 | self.set_extensions()
27 |
28 | def get_extensions(self):
29 | """ Get the extensions """
30 | return self.extensions
31 |
32 | def set_extensions(self):
33 | """ Set the extensions """
34 | self.extensions = []
35 |
36 | extensions = os.getenv('EXTENSIONS_' + self.browser.upper())
37 | if extensions is not None:
38 | extensions = Complement.convertENVArray(extensions)
39 |
40 | for extension in extensions:
41 | match extension:
42 | case 'metamask':
43 | self.start_wallet()
44 | case 'captcha':
45 | self.start_captcha()
46 |
47 | def start_captcha(self):
48 | """ Start the captcha """
49 | self.set_captcha()
50 | if self.captcha is not None:
51 | self.extensions.append(self.captcha.get_path_extension())
52 |
53 | def set_captcha(self):
54 | """ Set the captcha instance """
55 | self.captcha = CaptchaSolver(self.path_assets, self.browser,
56 | self.driver_action, self.driver)
57 | self.captcha.start()
58 |
59 | def get_captcha(self):
60 | """ Get the captcha instance """
61 | return self.captcha
62 |
63 | def start_wallet(self):
64 | """ Start the wallet """
65 | self.set_wallet()
66 | if self.wallet is not None:
67 | self.extensions.append(self.wallet.get_path_extension())
68 |
69 | def set_wallet(self):
70 | """ Set the wallet instance """
71 | self.wallet = MetaMask(self.path_assets, self.browser,
72 | self.driver_action, self.driver)
73 | self.wallet.start()
74 |
75 | def get_waller(self):
76 | """ Get the wallet instance """
77 | return self.wallet
78 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '44 0 * * 6'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/source/interface/extension/plugin_manager.py:
--------------------------------------------------------------------------------
1 | """
2 | Plugin Manager
3 | """
4 | from helper.complement import Complement
5 | from interface.interface import Interface
6 | from service.extension.download_extension_chrome import DownloadExtensionChrome
7 | from service.extension.download_extension_firefox import DownloadExtensionFirefox
8 |
9 |
10 | class PluginManager(metaclass=Interface):
11 | """ Plugin manager class """
12 | path_assets = ""
13 | path_data = ""
14 | # Chrome
15 | url_extension = ""
16 | # Firefox
17 | data_extension_firefox = {}
18 |
19 | def __init__(self, path_assets, browser, driver=None):
20 | self.path_assets = path_assets
21 | self.browser = browser
22 |
23 | self.driver = driver
24 |
25 | self.download_extension = None
26 | self.set_config()
27 |
28 | # @abstractmethod
29 | def set_config(self):
30 | """ Set config """
31 | self.set_browser()
32 | self.set_driver(self.driver)
33 |
34 | # @abstractmethod
35 | def set_browser(self):
36 | """ Set browser """
37 | if self.browser_is_chrome():
38 | self.download_extension = DownloadExtensionChrome(self.path_assets)
39 | elif self.browser_is_firefox():
40 | self.download_extension = DownloadExtensionFirefox(self.path_assets)
41 |
42 | # @abstractmethod
43 | def browser_is_chrome(self):
44 | """ Check if browser is chrome """
45 | return Complement.browser_is_chrome(self.browser)
46 |
47 | # @abstractmethod
48 | def browser_is_firefox(self):
49 | """ Check if browser is firefox """
50 | return Complement.browser_is_firefox(self.browser)
51 |
52 | # @abstractmethod
53 | def set_driver(self, driver):
54 | """ Set driver """
55 | self.driver = driver
56 | self.download_extension.set_driver(self.driver)
57 |
58 | # @abstractmethod
59 | def get_information_extension(self):
60 | """ Get information extension """
61 | url = self.url_extension
62 | return self.download_extension.exist_extension_by_url(url)
63 |
64 | # @abstractmethod
65 | def get_path_extension(self):
66 | """ Get path extension """
67 | check_extension = self.get_information_extension()
68 | return check_extension["path_file"]
69 |
70 | # @abstractmethod
71 | def extension_exist(self):
72 | """ Check if extension exist """
73 | check_extension = self.get_information_extension()
74 | return check_extension["exist"]
75 |
76 | # @abstractmethod
77 | def install(self):
78 | """ Install extension """
79 | if not self.extension_exist():
80 | self.download_extension.generate_extension(self.url_extension)
81 |
82 | # @abstractmethod
83 | def start(self):
84 | """ Start extension """
85 | if self.browser_is_firefox():
86 | self.url_extension = self.download_extension.get_url_extension(
87 | self.data_extension_firefox
88 | )
89 | self.install()
90 |
--------------------------------------------------------------------------------
/docker/single/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:focal
2 |
3 | ARG DEBIAN_FRONTEND=noninteractive
4 |
5 | ENV DISPLAY=:99
6 | ENV LANG C.UTF-8
7 | ENV LC_ALL C.UTF-8
8 | ENV PYTHONUNBUFFERED=1
9 | ENV PYTHON_VERSION_PROJECT 3.10
10 | ENV APP_HOME /usr/src/app
11 |
12 | WORKDIR /$APP_HOME
13 |
14 | # Install Python Version 3.10
15 | RUN apt-get update -y
16 |
17 | RUN apt-get install -y software-properties-common curl unzip wget
18 | RUN apt-add-repository ppa:deadsnakes/ppa
19 | RUN apt-get update -y
20 |
21 | RUN apt-get install -y python3.10
22 | RUN apt-get install -y python3.10-distutils
23 |
24 | RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1
25 | RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 2
26 | RUN update-alternatives --config python3
27 |
28 | RUN python3 --version
29 |
30 | RUN apt-get install -y python3-pip
31 | RUN apt-get install -y curl
32 |
33 | RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.10
34 | RUN pip install --upgrade pip
35 |
36 | # Install Browser
37 | RUN apt-get install --no-install-recommends -yqq \
38 | ca-certificates \
39 | gnupg2 \
40 | software-properties-common
41 |
42 | # install google chrome
43 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
44 | RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
45 | RUN apt-get -y update
46 | RUN apt-get install -y google-chrome-stable
47 |
48 | # install chromedriver
49 | RUN apt-get install -yqq unzip
50 | RUN wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip
51 | RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/
52 | RUN chmod +x /usr/local/bin/chromedriver
53 | RUN rm /tmp/chromedriver.zip
54 |
55 | RUN apt-get install --no-install-recommends -y \
56 | fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 \
57 | libnspr4 libnss3 lsb-release xdg-utils libxss1 libdbus-glib-1-2 libgbm1 \
58 | xvfb
59 |
60 |
61 | # Install firefox
62 | ENV FIREFOX_SETUP=firefox-setup.tar.bz2
63 | RUN apt-get purge firefox
64 | RUN wget -O $FIREFOX_SETUP "https://download.mozilla.org/?product=firefox-latest&os=linux64" && \
65 | tar xjf $FIREFOX_SETUP -C /opt/ && \
66 | ln -s /opt/firefox/firefox /usr/bin/firefox && \
67 | rm $FIREFOX_SETUP
68 |
69 | RUN echo "GECKODRIVER_VERSION="$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -oP '"tag_name": "\K(.*)(?=")') > /tmp/geckodriver_version
70 | RUN export $(cat /tmp/geckodriver_version) && \
71 | wget https://github.com/mozilla/geckodriver/releases/download/$GECKODRIVER_VERSION/geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz && \
72 | tar -zxf geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz -C /usr/local/bin && \
73 | chmod +x /usr/local/bin/geckodriver && \
74 | rm geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz && \
75 | rm /tmp/geckodriver_version
76 |
77 | # Generate App
78 | COPY ./source/ $APP_HOME/
79 | COPY .env $APP_HOME/
80 | RUN [ -f /$APP_HOME/.env ] || cp .env /$APP_HOME/
81 |
82 | RUN pip install -r requirements.txt
83 |
84 | # set display port to avoid crash
85 | CMD ["python3", "main.py"]
--------------------------------------------------------------------------------
/source/service/driver_manager.py:
--------------------------------------------------------------------------------
1 | """
2 | Driver Manager
3 | """
4 | from webdriver_manager.chrome import ChromeDriverManager
5 | from webdriver_manager.firefox import GeckoDriverManager
6 |
7 | from helper.complement import Complement
8 | from service.driver.configuration_chrome import ConfigurationChrome
9 | from service.driver.configuration_firefox import ConfigurationFirefox
10 |
11 |
12 | def download_chrome_driver():
13 | """ Download Chrome Driver """
14 | return ChromeDriverManager().install()
15 |
16 |
17 | def download_firefox_driver():
18 | """ Download Firefox Driver """
19 | return GeckoDriverManager().install()
20 |
21 |
22 | class DriverManager:
23 | """ Driver Manager """
24 | driver = None
25 | path_driver = None
26 | path_assets = None
27 |
28 | environment = 'local'
29 |
30 | browser = 'chrome'
31 | is_chrome = False
32 | is_firefox = False
33 | configuration_driver = None
34 |
35 | driver_chrome = "chromedriver"
36 | driver_firefox = "geckodriver"
37 |
38 | def __init__(self, path_assets, browser, environment):
39 | self.path_assets = path_assets
40 | self.browser = browser
41 | self.environment = environment
42 | self.init()
43 |
44 | def init(self):
45 | """ Initialize """
46 | self.is_chrome = Complement.browser_is_chrome(self.browser)
47 | self.is_firefox = Complement.browser_is_firefox(self.browser)
48 | self.path_driver = f"{self.path_assets}/driver/"
49 | self._set_path_driver()
50 | self.configure_driver()
51 |
52 | def _set_path_driver(self):
53 | """ Set Path Driver """
54 | driver_name = None
55 | if self.is_chrome:
56 | driver_name = self.driver_chrome
57 | elif self.is_firefox:
58 | driver_name = self.driver_firefox
59 |
60 | Complement.make_folder(self.path_driver)
61 | self.path_driver = self.path_driver + driver_name
62 |
63 | def setup_driver(self):
64 | """ Setup Driver """
65 | if not Complement.check_file_exist(self.path_driver) and self.environment == 'local':
66 | self.download_driver()
67 |
68 | def download_driver(self):
69 | """ Download Driver """
70 | driver_data = None
71 | if self.is_chrome:
72 | driver_data = download_chrome_driver()
73 | elif self.is_firefox:
74 | driver_data = download_firefox_driver()
75 |
76 | if driver_data is not None:
77 | Complement.move_file(driver_data, self.path_driver)
78 |
79 | def configure_driver(self):
80 | """ Configure Driver """
81 | config_single = None
82 | if self.is_chrome:
83 | config_single = ConfigurationChrome(
84 | self.path_driver, self.path_assets, self.environment
85 | )
86 | elif self.is_firefox:
87 | config_single = ConfigurationFirefox(
88 | self.path_driver, self.path_assets, self.environment
89 | )
90 |
91 | self.configuration_driver = config_single
92 |
93 | def configure_single(self):
94 | """ Configure Single """
95 | self.configuration_driver.set_driver()
96 |
97 | def get_driver(self):
98 | """ Get Driver """
99 | return self.configuration_driver.get_driver()
100 |
101 | def configure_master(self, path_extensions):
102 | """ Configure Master """
103 | self.configuration_driver.set_driver_extension(path_extensions)
104 |
--------------------------------------------------------------------------------
/source/service/driver/configuration_firefox.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | """
3 | Configuration Firefox driver
4 | """
5 | from selenium import webdriver
6 | from selenium.webdriver.firefox.service import Service as ServiceFirefox
7 |
8 | from helper.complement import Complement
9 | from interface.driver_interface import DriverInterface
10 |
11 |
12 | class ConfigurationFirefox(DriverInterface):
13 | """ Configuration Firefox driver """
14 |
15 | def __init__(self, path_driver, path_assets, environment):
16 | super().__init__(path_driver, path_assets, environment)
17 |
18 | def set_driver_extension(self, path_extensions):
19 | """ Set driver extension """
20 | options_browser = self._get_options()
21 | options_browser = self._get_extension_pre_config(options_browser)
22 | # options_browser = self._sign_extension(options_browser)
23 | self.driver = self._get_manager_driver(options_browser)
24 | self._set_extension_post_config(path_extensions)
25 |
26 | def _get_extension_pre_config(self, options_browser):
27 | """ Get extension pre config """
28 | options_browser.accept_untrusted_certs = True
29 | options_browser.accept_insecure_certs = True
30 | options_browser.set_preference('xpinstall.signatures.required', False)
31 | options_browser.set_preference('security.fileuri.strict_origin_policy', False)
32 | options_browser.set_preference("plugin.state.flash", 2)
33 | # options_browser.add_argument('--disable-popup-blocking')
34 |
35 | return options_browser
36 |
37 | def _set_extension_post_config(self, path_extensions):
38 | """ Set extension post config """
39 | for path_extension in path_extensions:
40 | self._set_manager_extension_post_config(path_extension)
41 |
42 | def _set_manager_extension_post_config(self, path_extension):
43 | """ Set manager extension post config """
44 | match self.environment:
45 | case 'local':
46 | self._set_extension_post_local(path_extension)
47 | case 'docker':
48 | self._set_extension_post_docker(path_extension)
49 | case 'remote':
50 | self._set_extension_post_remote(path_extension)
51 |
52 | def _set_extension_post_local(self, path_extension):
53 | """ Set extension post local """
54 | self.driver.install_addon(path_extension)
55 |
56 | def _set_extension_post_docker(self, path_extension):
57 | """ Set extension post docker """
58 | self.driver.install_addon(path_extension)
59 |
60 | def _set_extension_post_remote(self, path_extension):
61 | """ Set extension post remote """
62 | payload = {"path": path_extension, "temporary": True}
63 | # The function returns an identifier of the installed addon.
64 | var = self.driver.execute("INSTALL_ADDON", payload)["value"]
65 | # This identifier can later be used to uninstall installed addon.
66 | print(var)
67 |
68 | def _get_driver_local(self, options_browser):
69 | """ Get driver local """
70 | service = self._get_service()
71 | return webdriver.Firefox(service=service, options=options_browser)
72 |
73 | def _get_driver_docker(self, options_browser):
74 | """ Get driver docker """
75 | return webdriver.Firefox(options=options_browser)
76 |
77 | def _get_service(self):
78 | """ Get service """
79 | log_path = f"{self.path_assets}/log"
80 | Complement.make_folder(log_path)
81 |
82 | return ServiceFirefox(executable_path=self.path_driver, log_path=f"{log_path}/firefox.log")
83 |
84 | def _set_service_option(self):
85 | """ Set service option """
86 | return webdriver.FirefoxOptions()
87 |
--------------------------------------------------------------------------------
/source/service/extension/download_extension_chrome.py:
--------------------------------------------------------------------------------
1 | """
2 | Download Extension Chrome
3 | """
4 | import re
5 |
6 | from interface.extension.extension_manager import ExtensionManager
7 | from service.user_agent_browser import UserAgentBrowser
8 |
9 |
10 | def _get_version(user_agent):
11 | """ Get version """
12 | response = {"major": "", "minor": "", "build": "", "patch": ""}
13 | # Get Version
14 | regex = r"Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)"
15 | matches = re.finditer(regex, user_agent, re.MULTILINE)
16 | for match_num, match in enumerate(matches, start=1):
17 | match_value = match.group()
18 | print(match_num)
19 |
20 | if match_value:
21 | code_value = match_value.split("/")[-1]
22 | if code_value:
23 | value = code_value.split(".")
24 | if len(value) == 4:
25 | response = {
26 | "major": value[0],
27 | "minor": value[1],
28 | "build": value[2],
29 | "patch": value[3],
30 | }
31 | # Make Again version
32 | version = (
33 | response["major"]
34 | + "."
35 | + response["minor"]
36 | + "."
37 | + response["build"]
38 | + "."
39 | + response["patch"]
40 | )
41 | # Return version
42 | return version
43 |
44 |
45 | def _get_arch(user_agent):
46 | """ Get arch """
47 | nacl_arch = "arm"
48 | if user_agent.find("x86") > 0:
49 | nacl_arch = "x86-32"
50 | elif user_agent.find("x64") > 0:
51 | nacl_arch = "x86-64"
52 | return nacl_arch
53 |
54 |
55 | class DownloadExtensionChrome(ExtensionManager):
56 | """ Class for downloading extensions for Chrome """
57 | extension = "crx"
58 | path_file = "extensions/chrome"
59 | path_assets = None
60 | browser = 'chrome'
61 | driver = None
62 |
63 | MAIN_URL = "https://clients2.google.com/service/update2/crx?response=redirect&" \
64 | "prodversion={version}&acceptformat=crx2,crx3" \
65 | "&x=id%3D{id_extension}%26uc" \
66 | "&nacl_arch={nacl_arch}"
67 |
68 | def __init__(self, path_assets):
69 | super().__init__()
70 | self.path_assets = path_assets
71 | self.path_file = f"{path_assets}/{self.path_file}"
72 |
73 | def _get_user_agent_browser(self):
74 | """ Get user agent browser """
75 | user_agent_browser = UserAgentBrowser(self.path_assets, self.browser, self.driver)
76 | return user_agent_browser.data_user_agent()
77 |
78 | def _make_url(self, id_extension):
79 | """ Make url """
80 | user_agent = self._get_user_agent_browser()
81 | version = _get_version(user_agent)
82 | nacl_arch = _get_arch(user_agent)
83 | url = (self.MAIN_URL.format(
84 | version=version, id_extension=id_extension, nacl_arch=nacl_arch
85 | )
86 | )
87 | return url
88 |
89 | def _get_info(self, url_base_extension):
90 | """ Get info extension """
91 | data_main = url_base_extension.split("/")
92 | extension_id = data_main[-1]
93 | extension_name = data_main[-2]
94 | file_name = f"{extension_name}.{self.extension}"
95 | return {
96 | "extension_id": extension_id,
97 | "extension_name": extension_name,
98 | "file_name": file_name,
99 | }
100 |
101 | def _get_data_extension(self, url_ext):
102 | """ Get data extension """
103 | info_extension = self._get_info(url_ext)
104 | path_file = self._get_path_extension(url_ext, info_extension)
105 | url = self._make_url(info_extension["extension_id"])
106 | return {"url": url, "path_file": path_file}
107 |
108 | def generate_extension(self, url):
109 | """ Generate extension """
110 | data = self._get_data_extension(url)
111 | url = data["url"]
112 | path_file = data["path_file"]
113 | self._download_extension(url, path_file)
114 | return data["path_file"]
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker + Docker-compose + Selenium + Python
2 |
3 |
4 |

5 |
6 |
7 | This is a simple example of how to use Docker, Docker-compose and Selenium together to run a simple Python script.
8 |
9 | In this case, we can use three different ways to run the script:
10 |
11 | 1. Native
12 | 2. Dockerfile
13 | 3. Docker-compose with Simulated Web Driver
14 |
15 | ## Native
16 |
17 | To run the script natively, we can use the next commands.
18 |
19 | 1. You need to install Chrome or Firefox.
20 |
21 | 2. Start the workspace.
22 |
23 | ```shell
24 | virtualenv venv
25 | source venv/bin/activate
26 | ```
27 |
28 | 3. Edit the file .env to the workspace.
29 |
30 | As example, can be `source/.env`
31 |
32 | ```dotenv
33 | ENVIRONMENT=local
34 | #ENVIRONMENT=docker
35 | #ENVIRONMENT=remote
36 |
37 | PATH_ASSETS=assets
38 |
39 | #BROWSER=chrome
40 | #EXTENSIONS_CHROME=["metamask"]
41 |
42 | BROWSER=firefox
43 | EXTENSIONS_FIREFOX=["metamask", "captcha"]
44 |
45 | REMOTE_URL=http://selenium-hub:4444/wd/hub
46 | #REMOTE_URL=http://0.0.0.0:4444/wd/hub
47 | #REMOTE_URL=http://localhost:4444/wd/hub
48 |
49 | METAMASK_AUTH=["SECRET", "A B C D E F G H I J K L"]
50 | ```
51 |
52 | 4. Inside to the folder `source`
53 |
54 | ```shell
55 | cd source
56 | ```
57 |
58 | 5. Install the dependencies.
59 |
60 | ```shell
61 | python -m pip install --upgrade pip
62 | pip install -r requirements.txt
63 | ```
64 |
65 | 6. Execute the script.
66 |
67 | ```shell
68 | python main.py
69 | ```
70 |
71 | 7. Stop the workspace.
72 |
73 | ```shell
74 | deactivate
75 | ```
76 |
77 | ## Dockerfile
78 |
79 | 1. Edit the file .env to the workspace.
80 |
81 | As example, can be `source/.env`
82 |
83 | ```dotenv
84 | #ENVIRONMENT=local
85 | ENVIRONMENT=docker
86 | #ENVIRONMENT=remote
87 |
88 | PATH_ASSETS=assets
89 |
90 | BROWSER=chrome
91 | EXTENSIONS_CHROME=["metamask", "captcha"]
92 |
93 | #BROWSER=firefox
94 | #EXTENSIONS_FIREFOX=["metamask"]
95 |
96 | REMOTE_URL=http://selenium-hub:4444/wd/hub
97 | #REMOTE_URL=http://0.0.0.0:4444/wd/hub
98 | #REMOTE_URL=http://localhost:4444/wd/hub
99 |
100 | METAMASK_AUTH=["SECRET", "A B C D E F G H I J K L"]
101 |
102 | ```
103 |
104 | 2. Build the Docker image.
105 |
106 | ```shell
107 | docker build -f docker/single/Dockerfile -t selenium-python-docker .
108 | ```
109 |
110 | 3. Run the Docker image.
111 |
112 | ```shell
113 | docker run -it --rm -v $(pwd)/source:/usr/src/app -w /usr/src/app --env-file=.env selenium-python-docker
114 | ```
115 |
116 | ## Docker-compose
117 |
118 | 1. Edit the file .env to the workspace.
119 |
120 | As example, can be `source/.env`
121 |
122 | ```dotenv
123 | #ENVIRONMENT=local
124 | #ENVIRONMENT=docker
125 | ENVIRONMENT=remote
126 |
127 | PATH_ASSETS=assets
128 |
129 | BROWSER=chrome
130 | EXTENSIONS_CHROME=["metamask"]
131 |
132 | #BROWSER=firefox
133 | #EXTENSIONS_FIREFOX=["metamask"]
134 |
135 | REMOTE_URL=http://selenium-hub:4444/wd/hub
136 | #REMOTE_URL=http://0.0.0.0:4444/wd/hub
137 | #REMOTE_URL=http://localhost:4444/wd/hub
138 |
139 | METAMASK_AUTH=["SECRET", "A B C D E F G H I J K L"]
140 |
141 | ```
142 |
143 | 2. Build the Docker image.
144 |
145 | ```shell
146 | docker-compose build
147 | ```
148 |
149 | 3. Up the Docker image.
150 |
151 | ```shell
152 | docker-compose up
153 | ```
154 |
155 | 4. Execute the script in docker service.
156 |
157 | ```shell
158 | docker-compose exec -it app bash
159 | ```
160 |
161 | Or execute the script in the container.
162 |
163 | ```shell
164 | docker exec -it $(docker ps | grep app | awk '{print $1}') bash
165 | # python main.py
166 | ```
167 |
168 | 5. In the localhost:4000, you can see the result in session, note: the password of session vnc is `secret`
169 |
170 | 6. Stop the Docker image.
171 |
172 | ```shell
173 | docker-compose down
174 | ```
175 |
--------------------------------------------------------------------------------
/source/interface/driver_interface.py:
--------------------------------------------------------------------------------
1 | """
2 | Driver interface for the robot.
3 | """
4 | import json
5 | import os
6 | import uuid
7 | from abc import abstractmethod
8 |
9 | from selenium import webdriver
10 |
11 | from interface.interface import Interface
12 |
13 |
14 | class DriverInterface(metaclass=Interface):
15 | """ Driver interface for the robot. """
16 | driver = None
17 | path_driver = None
18 | path_assets = None
19 | environment = 'local'
20 |
21 | def __init__(self, path_driver, path_assets, environment):
22 | self.path_driver = path_driver
23 | self.path_assets = path_assets
24 | self.environment = environment
25 |
26 | @abstractmethod
27 | def set_driver(self):
28 | """ Set driver for the browser. """
29 | options_browser = self._get_options()
30 | self.driver = self._get_manager_driver(options_browser)
31 |
32 | @abstractmethod
33 | def set_driver_extension(self, path_extensions):
34 | """ Set driver for the browser. """
35 | options_browser = self._get_options()
36 | options_browser = self._get_extension_pre_config(options_browser)
37 | # options_browser = self._sign_extension(options_browser)
38 | self.driver = self._get_manager_driver(options_browser)
39 |
40 | @abstractmethod
41 | def _get_extension_pre_config(self, options_browser):
42 | """ Set driver for the browser. """
43 | options_browser.accept_untrusted_certs = True
44 | options_browser.accept_insecure_certs = True
45 | return options_browser
46 |
47 | @abstractmethod
48 | def _sign_extension(self, options_browser):
49 | """ Sign extension. """
50 | addon_id = "webextension@metamask.io"
51 | json_info = json.dumps({addon_id: str(uuid.uuid4())})
52 | options_browser.set_preference("extensions.webextensions.uuids", json_info)
53 | return options_browser
54 |
55 | @abstractmethod
56 | def get_driver(self):
57 | """ Get driver for the browser. """
58 | return self.driver
59 |
60 | @abstractmethod
61 | def _get_manager_driver(self, options_browser):
62 | match self.environment:
63 | case 'local':
64 | return self._get_driver_local(options_browser)
65 | case 'docker':
66 | return self._get_driver_docker(options_browser)
67 | case 'remote':
68 | return self._get_driver_remote(options_browser)
69 | case default:
70 | return None
71 |
72 | @abstractmethod
73 | def _get_driver_local(self, options_browser):
74 | service = self._get_service()
75 | # webdriver.Firefox or webdriver.Chrome
76 |
77 | @abstractmethod
78 | def _get_driver_docker(self, options_browser):
79 | # webdriver.Firefox or webdriver.Chrome
80 | return None
81 |
82 | @abstractmethod
83 | def _get_driver_remote(self, options_browser):
84 | remote_url = os.getenv("REMOTE_URL") or "http://selenium-hub:4444/wd/hub"
85 | return webdriver.Remote(
86 | command_executor=remote_url,
87 | options=options_browser
88 | )
89 |
90 | @abstractmethod
91 | def _get_service(self):
92 | # ServiceFirefox or ServiceChrome
93 | return None
94 |
95 | @abstractmethod
96 | def _get_options(self):
97 | options_browser = self._set_service_option()
98 | options_browser = self._get_options_general(options_browser)
99 | options_browser = self._get_manager_options(options_browser)
100 | return options_browser
101 |
102 | @abstractmethod
103 | def _set_service_option(self):
104 | # return webdriver.FirefoxOptions() webdriver.ChromeOptions()
105 | return None
106 |
107 | @abstractmethod
108 | def _get_manager_options(self, options_browser):
109 | match self.environment:
110 | case 'local':
111 | return self._get_options_local(options_browser)
112 | case 'docker':
113 | return self._get_options_docker(options_browser)
114 | case 'remote':
115 | return self._get_options_remote(options_browser)
116 | case default:
117 | return options_browser
118 |
119 | @abstractmethod
120 | def _get_options_general(self, options_browser):
121 | options_browser.add_argument("--lang=en-US")
122 | options_browser.add_argument("--disable-infobars")
123 | return options_browser
124 |
125 | @abstractmethod
126 | def _get_options_local(self, options_browser):
127 | options_browser.add_argument("--start-maximized")
128 | options_browser.add_argument("--disk-cache-size=1")
129 | options_browser.add_argument("--media-cache-size=1")
130 | options_browser.add_argument("--disable-application-cache")
131 | return options_browser
132 |
133 | @abstractmethod
134 | def _get_options_docker(self, options_browser):
135 | options_browser.add_argument("--headless")
136 | return options_browser
137 |
138 | @abstractmethod
139 | def _get_options_remote(self, options_browser):
140 | return options_browser
141 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### JetBrains template
2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
4 |
5 | # User-specific stuff
6 | .idea/**/workspace.xml
7 | .idea/**/tasks.xml
8 | .idea/**/usage.statistics.xml
9 | .idea/**/dictionaries
10 | .idea/**/shelf
11 |
12 | # Generated files
13 | .idea/**/contentModel.xml
14 |
15 | # Sensitive or high-churn files
16 | .idea/**/dataSources/
17 | .idea/**/dataSources.ids
18 | .idea/**/dataSources.local.xml
19 | .idea/**/sqlDataSources.xml
20 | .idea/**/dynamic.xml
21 | .idea/**/uiDesigner.xml
22 | .idea/**/dbnavigator.xml
23 |
24 | # Gradle
25 | .idea/**/gradle.xml
26 | .idea/**/libraries
27 |
28 | # Gradle and Maven with auto-import
29 | # When using Gradle or Maven with auto-import, you should exclude module files,
30 | # since they will be recreated, and may cause churn. Uncomment if using
31 | # auto-import.
32 | # .idea/artifacts
33 | # .idea/compiler.xml
34 | # .idea/jarRepositories.xml
35 | # .idea/modules.xml
36 | # .idea/*.iml
37 | # .idea/modules
38 | # *.iml
39 | # *.ipr
40 |
41 | # CMake
42 | cmake-build-*/
43 |
44 | # Mongo Explorer plugin
45 | .idea/**/mongoSettings.xml
46 |
47 | # File-based project format
48 | *.iws
49 |
50 | # IntelliJ
51 | out/
52 |
53 | # mpeltonen/sbt-idea plugin
54 | .idea_modules/
55 |
56 | # JIRA plugin
57 | atlassian-ide-plugin.xml
58 |
59 | # Cursive Clojure plugin
60 | .idea/replstate.xml
61 |
62 | # Crashlytics plugin (for Android Studio and IntelliJ)
63 | com_crashlytics_export_strings.xml
64 | crashlytics.properties
65 | crashlytics-build.properties
66 | fabric.properties
67 |
68 | # Editor-based Rest Client
69 | .idea/httpRequests
70 |
71 | # Android studio 3.1+ serialized cache file
72 | .idea/caches/build_file_checksums.ser
73 |
74 | ### VirtualEnv template
75 | # Virtualenv
76 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
77 | .Python
78 | [Bb]in
79 | [Ii]nclude
80 | [Ll]ib
81 | [Ll]ib64
82 | [Ll]ocal
83 | [Ss]cripts
84 | pyvenv.cfg
85 | .venv
86 | pip-selfcheck.json
87 |
88 | ### macOS template
89 | # General
90 | .DS_Store
91 | .AppleDouble
92 | .LSOverride
93 |
94 | # Icon must end with two \r
95 | Icon
96 |
97 | # Thumbnails
98 | ._*
99 |
100 | # Files that might appear in the root of a volume
101 | .DocumentRevisions-V100
102 | .fseventsd
103 | .Spotlight-V100
104 | .TemporaryItems
105 | .Trashes
106 | .VolumeIcon.icns
107 | .com.apple.timemachine.donotpresent
108 |
109 | # Directories potentially created on remote AFP share
110 | .AppleDB
111 | .AppleDesktop
112 | Network Trash Folder
113 | Temporary Items
114 | .apdisk
115 |
116 | ### Python template
117 | # Byte-compiled / optimized / DLL files
118 | __pycache__/
119 | **/__pycache__/
120 | *.py[cod]
121 | *$py.class
122 |
123 | # C extensions
124 | *.so
125 |
126 | # Distribution / packaging
127 | .Python
128 | build/
129 | develop-eggs/
130 | dist/
131 | downloads/
132 | eggs/
133 | .eggs/
134 | lib/
135 | lib64/
136 | parts/
137 | sdist/
138 | var/
139 | wheels/
140 | share/python-wheels/
141 | *.egg-info/
142 | .installed.cfg
143 | *.egg
144 | MANIFEST
145 |
146 | # PyInstaller
147 | # Usually these files are written by a python script from a template
148 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
149 | *.manifest
150 | *.spec
151 |
152 | # Installer logs
153 | pip-log.txt
154 | pip-delete-this-directory.txt
155 |
156 | # Unit test / coverage reports
157 | htmlcov/
158 | .tox/
159 | .nox/
160 | .coverage
161 | .coverage.*
162 | .cache
163 | nosetests.xml
164 | coverage.xml
165 | *.cover
166 | *.py,cover
167 | .hypothesis/
168 | .pytest_cache/
169 | cover/
170 |
171 | # Translations
172 | *.mo
173 | *.pot
174 |
175 | # Django stuff:
176 | *.log
177 | local_settings.py
178 | db.sqlite3
179 | db.sqlite3-journal
180 |
181 | # Flask stuff:
182 | instance/
183 | .webassets-cache
184 |
185 | # Scrapy stuff:
186 | .scrapy
187 |
188 | # Sphinx documentation
189 | docs/_build/
190 |
191 | # PyBuilder
192 | .pybuilder/
193 | target/
194 |
195 | # Jupyter Notebook
196 | .ipynb_checkpoints
197 |
198 | # IPython
199 | profile_default/
200 | ipython_config.py
201 |
202 | # pyenv
203 | # For a library or package, you might want to ignore these files since the code is
204 | # intended to run in multiple environments; otherwise, check them in:
205 | # .python-version
206 |
207 | # pipenv
208 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
209 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
210 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
211 | # install all needed dependencies.
212 | #Pipfile.lock
213 |
214 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
215 | __pypackages__/
216 |
217 | # Celery stuff
218 | celerybeat-schedule
219 | celerybeat.pid
220 |
221 | # SageMath parsed files
222 | *.sage.py
223 |
224 | # Environments
225 | .env
226 | .venv
227 | env/
228 | venv/
229 | ENV/
230 | env.bak/
231 | venv.bak/
232 |
233 | # Spyder project settings
234 | .spyderproject
235 | .spyproject
236 |
237 | # Rope project settings
238 | .ropeproject
239 |
240 | # mkdocs documentation
241 | /site
242 |
243 | # mypy
244 | .mypy_cache/
245 | .dmypy.json
246 | dmypy.json
247 |
248 | # Pyre type checker
249 | .pyre/
250 |
251 | # pytype static type analyzer
252 | .pytype/
253 |
254 | # Cython debug symbols
255 | cython_debug/
256 |
257 | !/assets/
258 | !/source/assets/
259 | /source/assets/
260 | ./source/assets/
261 | source/venv
262 | venv
--------------------------------------------------------------------------------
/source/controller/web_driver.py:
--------------------------------------------------------------------------------
1 | """
2 | WebDriver class for the controller.
3 | """
4 | import os
5 |
6 | from controller.extension_manager import ExtensionManager
7 | from helper.complement import Complement
8 | from service.driver_action import DriverAction
9 | from service.driver_manager import DriverManager
10 | from service.user_agent_browser import UserAgentBrowser
11 |
12 |
13 | class WebDriver:
14 | """ WebDriver class for the controller. """
15 | path_assets = None
16 | browser = None
17 | environment = 'local'
18 | driver = None
19 | driver_manager = None
20 | driver_action = None
21 | extension_manager = None
22 |
23 | def __init__(self, path_assets, browser, environment):
24 | self.path_assets = path_assets
25 | self.browser = browser
26 | self.environment = environment
27 | self._init()
28 |
29 | def _init(self):
30 | self._start_driver()
31 |
32 | def _get_is_configured(self):
33 | path_file = f"{self.path_assets}/config_{self.browser}"
34 | return Complement.check_file_exist(path_file)
35 |
36 | def _start_driver(self):
37 | if not self._get_is_configured():
38 | self._configure_driver()
39 |
40 | self._download_extension()
41 | self._init_driver()
42 |
43 | def _configure_driver(self):
44 | self._init_driver_manager()
45 | self._install_driver()
46 | self._init_single_driver()
47 | self._install_user_agent()
48 | self._make_config_file()
49 | self.driver.quit()
50 |
51 | def _init_driver_manager(self):
52 | driver_manager = DriverManager(self.path_assets, self.browser, self.environment)
53 | self.driver_manager = driver_manager
54 |
55 | def _install_driver(self):
56 | self.driver_manager.setup_driver()
57 |
58 | def _check_managers(self):
59 | self.driver = None
60 | if self.driver_manager is None:
61 | self._init_driver_manager()
62 |
63 | def _init_single_driver(self):
64 | self.driver_manager.configure_single()
65 | self.driver = self.driver_manager.get_driver()
66 |
67 | def _init_driver(self):
68 | self._check_managers()
69 |
70 | path_extensions = self._get_path_extension()
71 | self.driver_manager.configure_master(path_extensions)
72 | self.driver = self.driver_manager.get_driver()
73 | self._set_driver_action()
74 | if path_extensions is not None and len(path_extensions) > 0:
75 | self._set_extension_manager()
76 |
77 | def _install_user_agent(self):
78 | user_agent_browser = UserAgentBrowser(self.path_assets, self.browser, self.driver)
79 | if not user_agent_browser.exist_user_agent():
80 | user_agent_browser.data_user_agent()
81 |
82 | def _make_config_file(self):
83 | Complement.write_file(f"{self.path_assets}/config_{self.browser}", "True")
84 |
85 | def get_driver_manager(self):
86 | """ Return the driver manager. """
87 | return self.driver_manager
88 |
89 | def get_driver(self):
90 | """ Return the driver. """
91 | return self.driver
92 |
93 | def _download_extension(self):
94 | self.extension_manager = ExtensionManager(self.path_assets, self.browser)
95 |
96 | def _set_extension_manager(self):
97 | self.extension_manager = None
98 | self.extension_manager = ExtensionManager(self.path_assets, self.browser,
99 | self.driver_action, self.driver)
100 |
101 | def _get_path_extension(self):
102 | if self.extension_manager is not None:
103 | return self.extension_manager.get_extensions()
104 | return []
105 |
106 | def _set_driver_action(self):
107 | is_chrome = Complement.browser_is_chrome(self.browser)
108 | is_firefox = Complement.browser_is_firefox(self.browser)
109 | self.driver_action = DriverAction(self.driver, is_chrome, is_firefox)
110 |
111 | def start(self):
112 | """ Start the driver """
113 | self.driver_action.set_setting_window()
114 |
115 | # Example:
116 | self.example_extensions()
117 |
118 | self.driver_action.close_window()
119 |
120 | def example_extensions(self):
121 | """ Example of use of the extension manager. """
122 | extensions = os.getenv('EXTENSIONS_' + self.browser.upper()) or None
123 | if extensions is None:
124 | # Normal
125 | self.driver_action.wait_time()
126 | print(self.driver_action.get_title())
127 | else:
128 | self.example_extensions_basic(extensions)
129 |
130 | def example_extensions_basic(self, extensions):
131 | """ Example extensions """
132 | extensions = Complement.convertENVArray(extensions)
133 |
134 | for extension in extensions:
135 | match extension:
136 | case 'metamask':
137 | # Metamask
138 | self.example_metamask()
139 | case 'captcha':
140 | # CAPTCHA
141 | self.example_captcha()
142 |
143 | def example_metamask(self):
144 | """ Example of metamask """
145 | keys = os.getenv('METAMASK_AUTH') or ""
146 | keys = Complement.convertENVArray(keys)
147 | self.extension_manager.wallet.set_passwords(keys[0], keys[1])
148 | self.extension_manager.wallet.login()
149 |
150 | def example_captcha(self):
151 | """ Example of CAPTCHA """
152 | self.extension_manager.captcha.set_test_url()
153 | self.extension_manager.captcha.resolve()
154 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = false
9 | max_line_length = 120
10 | tab_width = 4
11 | ij_continuation_indent_size = 8
12 | ij_formatter_off_tag = @formatter:off
13 | ij_formatter_on_tag = @formatter:on
14 | ij_formatter_tags_enabled = false
15 | ij_smart_tabs = false
16 | ij_visual_guides = none
17 | ij_wrap_on_typing = false
18 |
19 | [.editorconfig]
20 | ij_editorconfig_align_group_field_declarations = false
21 | ij_editorconfig_space_after_colon = false
22 | ij_editorconfig_space_after_comma = true
23 | ij_editorconfig_space_before_colon = false
24 | ij_editorconfig_space_before_comma = false
25 | ij_editorconfig_spaces_around_assignment_operators = true
26 |
27 | [{*.markdown,*.md}]
28 | ij_markdown_force_one_space_after_blockquote_symbol = true
29 | ij_markdown_force_one_space_after_header_symbol = true
30 | ij_markdown_force_one_space_after_list_bullet = true
31 | ij_markdown_force_one_space_between_words = true
32 | ij_markdown_insert_quote_arrows_on_wrap = true
33 | ij_markdown_keep_indents_on_empty_lines = false
34 | ij_markdown_keep_line_breaks_inside_text_blocks = true
35 | ij_markdown_max_lines_around_block_elements = 1
36 | ij_markdown_max_lines_around_header = 1
37 | ij_markdown_max_lines_between_paragraphs = 1
38 | ij_markdown_min_lines_around_block_elements = 1
39 | ij_markdown_min_lines_around_header = 1
40 | ij_markdown_min_lines_between_paragraphs = 1
41 | ij_markdown_wrap_text_if_long = true
42 | ij_markdown_wrap_text_inside_blockquotes = true
43 |
44 | [{*.py,*.pyw}]
45 | ij_python_align_collections_and_comprehensions = true
46 | ij_python_align_multiline_imports = true
47 | ij_python_align_multiline_parameters = true
48 | ij_python_align_multiline_parameters_in_calls = true
49 | ij_python_blank_line_at_file_end = true
50 | ij_python_blank_lines_after_imports = 1
51 | ij_python_blank_lines_after_local_imports = 0
52 | ij_python_blank_lines_around_class = 1
53 | ij_python_blank_lines_around_method = 1
54 | ij_python_blank_lines_around_top_level_classes_functions = 2
55 | ij_python_blank_lines_before_first_method = 0
56 | ij_python_call_parameters_new_line_after_left_paren = false
57 | ij_python_call_parameters_right_paren_on_new_line = false
58 | ij_python_call_parameters_wrap = normal
59 | ij_python_dict_alignment = 0
60 | ij_python_dict_new_line_after_left_brace = false
61 | ij_python_dict_new_line_before_right_brace = false
62 | ij_python_dict_wrapping = 1
63 | ij_python_from_import_new_line_after_left_parenthesis = false
64 | ij_python_from_import_new_line_before_right_parenthesis = false
65 | ij_python_from_import_parentheses_force_if_multiline = false
66 | ij_python_from_import_trailing_comma_if_multiline = false
67 | ij_python_from_import_wrapping = 1
68 | ij_python_hang_closing_brackets = false
69 | ij_python_keep_blank_lines_in_code = 1
70 | ij_python_keep_blank_lines_in_declarations = 1
71 | ij_python_keep_indents_on_empty_lines = false
72 | ij_python_keep_line_breaks = true
73 | ij_python_method_parameters_new_line_after_left_paren = false
74 | ij_python_method_parameters_right_paren_on_new_line = false
75 | ij_python_method_parameters_wrap = normal
76 | ij_python_new_line_after_colon = false
77 | ij_python_new_line_after_colon_multi_clause = true
78 | ij_python_optimize_imports_always_split_from_imports = false
79 | ij_python_optimize_imports_case_insensitive_order = false
80 | ij_python_optimize_imports_join_from_imports_with_same_source = false
81 | ij_python_optimize_imports_sort_by_type_first = true
82 | ij_python_optimize_imports_sort_imports = true
83 | ij_python_optimize_imports_sort_names_in_from_imports = false
84 | ij_python_space_after_comma = true
85 | ij_python_space_after_number_sign = true
86 | ij_python_space_after_py_colon = true
87 | ij_python_space_before_backslash = true
88 | ij_python_space_before_comma = false
89 | ij_python_space_before_for_semicolon = false
90 | ij_python_space_before_lbracket = false
91 | ij_python_space_before_method_call_parentheses = false
92 | ij_python_space_before_method_parentheses = false
93 | ij_python_space_before_number_sign = true
94 | ij_python_space_before_py_colon = false
95 | ij_python_space_within_empty_method_call_parentheses = false
96 | ij_python_space_within_empty_method_parentheses = false
97 | ij_python_spaces_around_additive_operators = true
98 | ij_python_spaces_around_assignment_operators = true
99 | ij_python_spaces_around_bitwise_operators = true
100 | ij_python_spaces_around_eq_in_keyword_argument = false
101 | ij_python_spaces_around_eq_in_named_parameter = false
102 | ij_python_spaces_around_equality_operators = true
103 | ij_python_spaces_around_multiplicative_operators = true
104 | ij_python_spaces_around_power_operator = true
105 | ij_python_spaces_around_relational_operators = true
106 | ij_python_spaces_around_shift_operators = true
107 | ij_python_spaces_within_braces = false
108 | ij_python_spaces_within_brackets = false
109 | ij_python_spaces_within_method_call_parentheses = false
110 | ij_python_spaces_within_method_parentheses = false
111 | ij_python_use_continuation_indent_for_arguments = false
112 | ij_python_use_continuation_indent_for_collection_and_comprehensions = false
113 | ij_python_use_continuation_indent_for_parameters = true
114 | ij_python_wrap_long_lines = false
115 |
116 | [{*.yaml,*.yml}]
117 | indent_size = 2
118 | ij_yaml_align_values_properties = do_not_align
119 | ij_yaml_autoinsert_sequence_marker = true
120 | ij_yaml_block_mapping_on_new_line = false
121 | ij_yaml_indent_sequence_value = true
122 | ij_yaml_keep_indents_on_empty_lines = false
123 | ij_yaml_keep_line_breaks = true
124 | ij_yaml_sequence_on_new_line = false
125 | ij_yaml_space_before_colon = false
126 | ij_yaml_spaces_within_braces = true
127 | ij_yaml_spaces_within_brackets = true
128 |
--------------------------------------------------------------------------------
/source/service/driver/configuration_chrome.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | """
3 | Configuration for Chrome.
4 | """
5 | import os
6 |
7 | from selenium import webdriver
8 | from selenium.webdriver.chrome.service import Service as ServiceChrome
9 |
10 | from interface.driver_interface import DriverInterface
11 |
12 |
13 | class ConfigurationChrome(DriverInterface):
14 | """ Configuration Chrome class """
15 |
16 | def __init__(self, path_driver, path_assets, environment):
17 | super().__init__(path_driver, path_assets, environment)
18 |
19 | def set_driver_extension(self, path_extensions):
20 | """ Set driver extension """
21 | options_browser = self._get_options()
22 | options_browser = self._set_extension_pre_config(options_browser, path_extensions)
23 | self.driver = self._get_manager_driver(options_browser)
24 |
25 | def _get_extension_pre_config(self, options_browser):
26 | """ Get extension pre config """
27 | options_browser.accept_untrusted_certs = True
28 | options_browser.accept_insecure_certs = True
29 | # options_browser.add_argument('--disable-popup-blocking')
30 |
31 | return options_browser
32 |
33 | def _set_extension_pre_config(self, options_browser, path_extensions):
34 | """ Set extension pre config """
35 | options_browser = self._get_extension_pre_config(options_browser)
36 | for path_extension in path_extensions:
37 | options_browser = self._set_manager_extension_pre_config(options_browser, path_extension)
38 | return options_browser
39 |
40 | def _set_manager_extension_pre_config(self, options_browser, path_extension):
41 | """ Set manager extension pre config """
42 | match self.environment:
43 | case 'local':
44 | return self._set_extension_pre_local(options_browser, path_extension)
45 | case 'docker':
46 | return self._set_extension_pre_docker(options_browser, path_extension)
47 | case 'remote':
48 | return self._set_extension_pre_remote(options_browser, path_extension)
49 | case default:
50 | return options_browser
51 |
52 | def _set_extension_pre_local(self, options_browser, path_extension):
53 | """ Set extension pre local """
54 | # options_browser.add_argument(f'--load-extension = {extension}')
55 | options_browser.add_extension(path_extension)
56 | return options_browser
57 |
58 | def _set_extension_pre_docker(self, options_browser, path_extension):
59 | """ Set extension pre docker """
60 | # options_browser.add_argument(f'--load-extension = {extension}')
61 | options_browser.add_extension(path_extension)
62 | return options_browser
63 |
64 | def _set_extension_pre_remote(self, options_browser, path_extension):
65 | """ Set extension pre remote """
66 | # options_browser.add_argument(f'--load-extension = {extension}')
67 | options_browser.add_extension(path_extension)
68 | return options_browser
69 |
70 | def _get_driver_local(self, options_browser):
71 | """ Get driver local """
72 | service = self._get_service()
73 | return webdriver.Chrome(service=service, options=options_browser)
74 |
75 | def _get_driver_docker(self, options_browser):
76 | """ Get driver docker """
77 | return webdriver.Chrome(options=options_browser)
78 |
79 | def _get_service(self):
80 | """ Get service """
81 | service_browser = ServiceChrome(self.path_driver)
82 | return service_browser
83 |
84 | def _set_service_option(self):
85 | """ Set service option """
86 | return webdriver.ChromeOptions()
87 |
88 | def _get_options_general(self, options_browser):
89 | """ Get options general """
90 | options_browser.add_argument("--lang=en-US")
91 | options_browser.add_argument("--disable-infobars")
92 | options_browser.add_argument("--start-maximized")
93 | options_browser.add_experimental_option(
94 | "prefs",
95 | {
96 | "intl.accept_languages": "en,en_US",
97 | "download.default_directory": os.getcwd(),
98 | "download.prompt_for_download": False,
99 | },
100 | )
101 | options_browser.add_experimental_option("excludeSwitches", ["enable-logging"])
102 | options_browser.add_experimental_option("excludeSwitches", ["enable-automation"])
103 | options_browser.add_experimental_option("useAutomationExtension", False)
104 | return options_browser
105 |
106 | def _get_options_local(self, options_browser):
107 | """ Get options local """
108 | # options_browser.add_argument("--no-sandbox")
109 | options_browser.add_argument("--log-level=3")
110 | options_browser.add_argument("--disable-gpu")
111 | options_browser.add_argument("--no-first-run")
112 | options_browser.add_argument("--disk-cache-size=1")
113 | options_browser.add_argument("--media-cache-size=1")
114 | options_browser.add_argument("--no-service-autorun")
115 | options_browser.add_argument("--password-store=basic")
116 | # options_browser.add_argument("--disable-dev-shm-usage")
117 | options_browser.add_argument("--disable-application-cache")
118 | options_browser.add_argument("--disable-blink-features=AutomationControlled")
119 | return options_browser
120 |
121 | def _get_options_docker(self, options_browser):
122 | """ Get options docker """
123 | # options_browser.add_argument('--headless')
124 | options_browser.add_argument('--no-sandbox')
125 | options_browser.add_argument('--disable-dev-shm-usage')
126 | return options_browser
127 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | ### VisualStudioCode template
2 | .vscode/*
3 | !.vscode/settings.json
4 | !.vscode/tasks.json
5 | !.vscode/launch.json
6 | !.vscode/extensions.json
7 | *.code-workspace
8 |
9 | # Local History for Visual Studio Code
10 | .history/
11 |
12 | ### JetBrains template
13 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
14 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
15 |
16 | # User-specific stuff
17 | .idea/**/workspace.xml
18 | .idea/**/tasks.xml
19 | .idea/**/usage.statistics.xml
20 | .idea/**/dictionaries
21 | .idea/**/shelf
22 |
23 | # Generated files
24 | .idea/**/contentModel.xml
25 |
26 | # Sensitive or high-churn files
27 | .idea/**/dataSources/
28 | .idea/**/dataSources.ids
29 | .idea/**/dataSources.local.xml
30 | .idea/**/sqlDataSources.xml
31 | .idea/**/dynamic.xml
32 | .idea/**/uiDesigner.xml
33 | .idea/**/dbnavigator.xml
34 |
35 | # Gradle
36 | .idea/**/gradle.xml
37 | .idea/**/libraries
38 |
39 | # Gradle and Maven with auto-import
40 | # When using Gradle or Maven with auto-import, you should exclude module files,
41 | # since they will be recreated, and may cause churn. Uncomment if using
42 | # auto-import.
43 | # .idea/artifacts
44 | # .idea/compiler.xml
45 | # .idea/jarRepositories.xml
46 | # .idea/modules.xml
47 | # .idea/*.iml
48 | # .idea/modules
49 | # *.iml
50 | # *.ipr
51 |
52 | # CMake
53 | cmake-build-*/
54 |
55 | # Mongo Explorer plugin
56 | .idea/**/mongoSettings.xml
57 |
58 | # File-based project format
59 | *.iws
60 |
61 | # IntelliJ
62 | out/
63 |
64 | # mpeltonen/sbt-idea plugin
65 | .idea_modules/
66 |
67 | # JIRA plugin
68 | atlassian-ide-plugin.xml
69 |
70 | # Cursive Clojure plugin
71 | .idea/replstate.xml
72 |
73 | # Crashlytics plugin (for Android Studio and IntelliJ)
74 | com_crashlytics_export_strings.xml
75 | crashlytics.properties
76 | crashlytics-build.properties
77 | fabric.properties
78 |
79 | # Editor-based Rest Client
80 | .idea/httpRequests
81 |
82 | # Android studio 3.1+ serialized cache file
83 | .idea/caches/build_file_checksums.ser
84 |
85 | ### VirtualEnv template
86 | # Virtualenv
87 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
88 | .Python
89 | [Bb]in
90 | [Ii]nclude
91 | [Ll]ib
92 | [Ll]ib64
93 | [Ll]ocal
94 | [Ss]cripts
95 | pyvenv.cfg
96 | .venv
97 | pip-selfcheck.json
98 |
99 | ### Vim template
100 | # Swap
101 | [._]*.s[a-v][a-z]
102 | !*.svg # comment out if you don't need vector files
103 | [._]*.sw[a-p]
104 | [._]s[a-rt-v][a-z]
105 | [._]ss[a-gi-z]
106 | [._]sw[a-p]
107 |
108 | # Session
109 | Session.vim
110 | Sessionx.vim
111 |
112 | # Temporary
113 | .netrwhist
114 | *~
115 | # Auto-generated tag files
116 | tags
117 | # Persistent undo
118 | [._]*.un~
119 |
120 | ### macOS template
121 | # General
122 | .DS_Store
123 | .AppleDouble
124 | .LSOverride
125 |
126 | # Icon must end with two \r
127 | Icon
128 |
129 | # Thumbnails
130 | ._*
131 |
132 | # Files that might appear in the root of a volume
133 | .DocumentRevisions-V100
134 | .fseventsd
135 | .Spotlight-V100
136 | .TemporaryItems
137 | .Trashes
138 | .VolumeIcon.icns
139 | .com.apple.timemachine.donotpresent
140 |
141 | # Directories potentially created on remote AFP share
142 | .AppleDB
143 | .AppleDesktop
144 | Network Trash Folder
145 | Temporary Items
146 | .apdisk
147 |
148 | ### Python template
149 | # Byte-compiled / optimized / DLL files
150 | __pycache__/
151 | *.py[cod]
152 | *$py.class
153 |
154 | # C extensions
155 | *.so
156 |
157 | # Distribution / packaging
158 | .Python
159 | build/
160 | develop-eggs/
161 | dist/
162 | downloads/
163 | eggs/
164 | .eggs/
165 | lib/
166 | lib64/
167 | parts/
168 | sdist/
169 | var/
170 | wheels/
171 | share/python-wheels/
172 | *.egg-info/
173 | .installed.cfg
174 | *.egg
175 | MANIFEST
176 |
177 | # PyInstaller
178 | # Usually these files are written by a python script from a template
179 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
180 | *.manifest
181 | *.spec
182 |
183 | # Installer logs
184 | pip-log.txt
185 | pip-delete-this-directory.txt
186 |
187 | # Unit test / coverage reports
188 | htmlcov/
189 | .tox/
190 | .nox/
191 | .coverage
192 | .coverage.*
193 | .cache
194 | nosetests.xml
195 | coverage.xml
196 | *.cover
197 | *.py,cover
198 | .hypothesis/
199 | .pytest_cache/
200 | cover/
201 |
202 | # Translations
203 | *.mo
204 | *.pot
205 |
206 | # Django stuff:
207 | *.log
208 | local_settings.py
209 | db.sqlite3
210 | db.sqlite3-journal
211 |
212 | # Flask stuff:
213 | instance/
214 | .webassets-cache
215 |
216 | # Scrapy stuff:
217 | .scrapy
218 |
219 | # Sphinx documentation
220 | docs/_build/
221 |
222 | # PyBuilder
223 | .pybuilder/
224 | target/
225 |
226 | # Jupyter Notebook
227 | .ipynb_checkpoints
228 |
229 | # IPython
230 | profile_default/
231 | ipython_config.py
232 |
233 | # pyenv
234 | # For a library or package, you might want to ignore these files since the code is
235 | # intended to run in multiple environments; otherwise, check them in:
236 | # .python-version
237 |
238 | # pipenv
239 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
240 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
241 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
242 | # install all needed dependencies.
243 | #Pipfile.lock
244 |
245 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
246 | __pypackages__/
247 |
248 | # Celery stuff
249 | celerybeat-schedule
250 | celerybeat.pid
251 |
252 | # SageMath parsed files
253 | *.sage.py
254 |
255 | # Environments
256 | .venv
257 | env/
258 | venv/
259 | ENV/
260 | env.bak/
261 | venv.bak/
262 |
263 | # Spyder project settings
264 | .spyderproject
265 | .spyproject
266 |
267 | # Rope project settings
268 | .ropeproject
269 |
270 | # mkdocs documentation
271 | /site
272 |
273 | # mypy
274 | .mypy_cache/
275 | .dmypy.json
276 | dmypy.json
277 |
278 | # Pyre type checker
279 | .pyre/
280 |
281 | # pytype static type analyzer
282 | .pytype/
283 |
284 | # Cython debug symbols
285 | cython_debug/
286 |
287 |
--------------------------------------------------------------------------------
/source/controller/extension/captchasolver.py:
--------------------------------------------------------------------------------
1 | """
2 | CAPTCHA solver for the extension.
3 | """
4 | import random
5 | import time
6 |
7 | from interface.extension.plugin_manager import PluginManager
8 |
9 |
10 | def sleep_seconds():
11 | """Sleep seconds"""
12 | min_rand = 0.64
13 | max_rand = 1.27
14 | time.sleep(random.uniform(min_rand, max_rand))
15 |
16 |
17 | class CaptchaSolver(PluginManager):
18 | """ CAPTCHA solver for the extension. """
19 |
20 | # Variables
21 | solver_challenge = False
22 | # Settings
23 | RECAPTCHA_PAGE_URL = "https://patrickhlauke.github.io/recaptcha"
24 | # RECAPTCHA_PAGE_URL = "https://www.google.com/recaptcha/api2/demo"
25 | # Chrome
26 | url_extension = (
27 | "https://chrome.google.com/webstore/detail/"
28 | "buster-captcha-solver-for/"
29 | "mpbjkejclgfgadiemmefgebjfooflfhl"
30 | )
31 | # Firefox
32 | data_extension_firefox = {
33 | "user_id": [12929064],
34 | "url": "https://addons.mozilla.org/en-US/firefox/"
35 | "addon/buster-captcha-solver",
36 | "collection_url": "https://addons.mozilla.org/en-US/"
37 | "firefox/addon/youtube-video-quality",
38 | }
39 |
40 | def __init__(self, path_assets, browser="chrome",
41 | driver_action=None, driver=None):
42 | self.path_assets = path_assets
43 | self.browser = browser
44 | self.driver_action = driver_action
45 | self.driver = driver
46 |
47 | self._init_variables()
48 |
49 | super().__init__(path_assets, browser, driver)
50 |
51 | def _init_variables(self):
52 | self.download_extension = None
53 | self.path_data = f"{self.path_assets}/{self.path_data}"
54 |
55 | def set_test_url(self):
56 | """Set the test url"""
57 | self.driver_action.set_setting_window(self.RECAPTCHA_PAGE_URL)
58 | sleep_seconds()
59 |
60 | def resolve(self):
61 | """Resolve the captcha Google"""
62 | if self._check_webdriver():
63 | self._resolve_captcha()
64 |
65 | def _check_webdriver(self):
66 | if self.driver_action is None or self.driver is None:
67 | return False
68 | if self.driver_action and self.driver:
69 | return True
70 | return False
71 |
72 | def _resolve_captcha(self):
73 | self._start_tries()
74 |
75 | def _start_tries(self):
76 | counter = 0
77 | number_of_iterations = 10
78 | for i in range(number_of_iterations):
79 | if self.solver_challenge:
80 | break
81 |
82 | if self._check_exist_captcha():
83 | self._solve()
84 | counter += 1
85 | else:
86 | break
87 | print(f"Successful breaks: {counter} - {i}")
88 |
89 | print(f"Total successful breaks: {counter}")
90 |
91 | def _check_exist_captcha(self):
92 | return self.driver_action.visible("//iframe[contains(@src,'recaptcha')]") or False
93 |
94 | def _solve(self):
95 | # Get a ReCaptcha Challenge
96 | if self._get_recaptcha_challenge():
97 | # Switch to page's main frame
98 | self.driver_action.switch_to_default_content()
99 | # Get audio challenge
100 | check_challenge = self._get_audio_challenge()
101 | if check_challenge:
102 | self.solver_challenge = check_challenge
103 | # Switch to the ReCaptcha iframe to verify it is solved
104 | self.driver_action.switch_to_default_content()
105 |
106 | def _get_recaptcha_challenge(self):
107 | iframes = self._get_all_iframes()
108 | if self._switch_recaptcha_iframe(iframes):
109 | self._click_recaptcha_checkbox()
110 | return self._check_recaptcha_has_challenge()
111 | return False
112 |
113 | def _get_all_iframes(self):
114 | iframes = self.driver_action.find_all_by_tag('iframe')
115 | if len(iframes) > 0:
116 | return iframes
117 | return []
118 |
119 | def _switch_recaptcha_iframe(self, iframes):
120 | # Switch focus to ReCaptcha iframe
121 | self.driver_action.switch_to_iframe(iframes[0])
122 | check_exist = self.driver_action.is_exists_by_xpath(
123 | '//div[@class="recaptcha-checkbox-checkmark" and @role="presentation"]')
124 | if not check_exist:
125 | print("No element in the frame!!")
126 | return check_exist
127 |
128 | def _click_recaptcha_checkbox(self):
129 | xpath = '//div[@class="recaptcha-checkbox-border"]'
130 | if self.driver_action.is_exists_by_xpath(xpath):
131 | self.driver_action.clickable(xpath)
132 |
133 | def _check_recaptcha_has_challenge(self):
134 | # Check if the ReCaptcha has no challenge
135 | exist = self.driver_action.is_exists_by_xpath('//span[@aria-checked="true"]')
136 | if exist:
137 | print("ReCaptcha has no challenge. Trying again!")
138 | return not exist
139 |
140 | def _get_audio_challenge(self):
141 | # Get all the iframes on the page again
142 | iframes = self._get_all_iframes()
143 | # Switch to the last iframe (the new one)
144 | self.driver_action.switch_to_iframe(iframes[-1])
145 | # Check and press Audio challenge button
146 | if self._check_audio_button():
147 | if not self._check_submit_button():
148 | return True
149 |
150 | return self._check_recaptcha_audio_button()
151 |
152 | def _check_recaptcha_audio_button(self):
153 | if not self._check_button('//button[@id="recaptcha-audio-button"]'):
154 | print("No element of audio challenge!!")
155 | return False
156 | return True
157 |
158 | def _check_audio_button(self):
159 | # Check if the audio challenge button is present
160 | rule_first = "contains(@class, 'button-holder')"
161 | rule_second = "contains(@class, 'help-button-holder')"
162 | xpath = f"//div[{rule_first} and {rule_second}]"
163 | # Check Audio Button
164 | check_exist = self._check_button(xpath)
165 | if check_exist:
166 | # Click on the Audio Button
167 | self._click_button(xpath)
168 | return check_exist
169 |
170 | def _check_submit_button(self):
171 | # Check again button is present
172 | rule_first = "contains(@class, 'rc-button-default')"
173 | rule_second = "contains(@class, 'goog-inline-block')"
174 | xpath = f"//*[{rule_first} and {rule_second}]"
175 | # Check Audio Button
176 | check_exist = self._check_button(xpath)
177 | if check_exist:
178 | # Click on the Audio Button
179 | self._click_button(xpath)
180 | return check_exist
181 |
182 | def _check_button(self, xpath):
183 | check_exist = self.driver_action.is_exists_by_xpath(xpath)
184 | if not check_exist:
185 | print("No exist button")
186 | return check_exist
187 |
188 | def _click_button(self, xpath):
189 | self.driver_action.clickable(xpath)
190 |
--------------------------------------------------------------------------------
/source/controller/extension/metamask.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | """
3 | Metamask extension for the controller.
4 | """
5 |
6 | from interface.extension.plugin_manager import PluginManager
7 |
8 |
9 | class MetaMask(PluginManager):
10 | """ Metamask extension for the controller. """
11 | path_data = "wallets/metamask"
12 | # Data
13 | recovery_phrase = ""
14 | password = ""
15 |
16 | # Chrome
17 | url_extension = (
18 | "https://chrome.google.com/webstore/detail/"
19 | "metamask/nkbihfbeogaeaoehlefnkodbefgpgknn"
20 | )
21 | # Firefox
22 | data_extension_firefox = {
23 | "url": "https://addons.mozilla.org/en-US/firefox/addon/ether-metamask",
24 | "collection_url": "https://addons.mozilla.org/en-US/firefox/"
25 | "addon/metamask-legacy-web3/",
26 | "user_id": [12436990, 13014139],
27 | }
28 |
29 | def __init__(self, path_assets, browser="chrome",
30 | driver_action=None, driver=None):
31 | self.path_assets = path_assets
32 | self.browser = browser
33 |
34 | self.driver_action = driver_action
35 | self.driver = driver
36 |
37 | self._init_variables()
38 |
39 | super().__init__(path_assets, browser, driver)
40 |
41 | def _init_variables(self):
42 | self.fails = 0
43 | self.private_key = None
44 | self.password = None
45 | self.recovery_phrase = None
46 | self.download_extension = None
47 | self.path_data = f"{self.path_assets}/{self.path_data}"
48 |
49 | # Init Process
50 | def set_passwords(self, password: str = None, recovery_phrase: str = None):
51 | """Set the password and the recovery phrase."""
52 | # question = "What is your MetaMask password?"
53 | self.password = password
54 | # question = "What is your MetaMask recovery phrase?"
55 | self.recovery_phrase = recovery_phrase
56 |
57 | # Selenium Process
58 |
59 | def login(self):
60 | """Login to the MetaMask extension."""
61 | try:
62 | # Switch to the MetaMask extension tab.
63 | self.driver_action.window_handles(0)
64 | # Prevent a blank page.
65 | self.driver.refresh()
66 | # Click on the "Start" button.
67 | self.driver_action.clickable('//*[@class="welcome-page"]/button')
68 | # Click on the "Import wallet" button.
69 | self.driver_action.clickable('//*[contains(@class, "btn-primary")][position()=1]')
70 | # Click on the "I agree" button.
71 | self.driver_action.clickable('//footer/button[2]')
72 | # Put the password
73 | self._set_data_password()
74 | # Wait until the login worked and click on the "All done" button.
75 | self.driver_action.visible('//*[contains(@class, "emoji")][position()=1]')
76 | # Confirm the connection to MetaMask.
77 | self.driver_action.clickable('//*[contains(@class, "btn-primary")][position()=1]')
78 | # Set Private Key
79 | self._set_private_key()
80 | except (Exception,): # Failed - a web element is not accessible.
81 | self.fails += 1 # Increment the counter.
82 | if self.fails < 2: # Retry login to the MetaMask.
83 | self.login()
84 | else: # Failed twice - the wallet is not accessible.
85 | self.driver.quit() # Stop the webdriver.
86 |
87 | def _set_data_password(self):
88 | # If a recovery phrase and password are set.
89 | if self.recovery_phrase != '' and self.password != '':
90 | # Count the words in the recovery phrase.
91 | split_recovery_phrase = self.recovery_phrase.split(" ")
92 | count = len(split_recovery_phrase)
93 | # Select by length the recovery phrase.
94 | xpath = '//select[contains(@class, "dropdown__select")]'
95 | self.driver_action.select_by_value(xpath, count)
96 | # Put the recovery phrase.
97 | for i in range(count):
98 | # Get the word.
99 | word = split_recovery_phrase[i]
100 | # Set the word.
101 | self.driver_action.send_keys(f'//*[@id="import-srp__srp-word-{i}"]', word)
102 | # Input a password of your account.
103 | for path in ('//*[@id="password"]', '//*[@id="confirm-password"]'):
104 | self.driver_action.send_keys(path, self.password)
105 | # Click on the "I have read and agree to the..." checkbox.
106 | self.driver_action.clickable('(//*[@id="create-new-vault__terms-checkbox"])[1]')
107 | # Click on the "Import" button.
108 | self.driver_action.clickable('//*[contains(@class, "btn-primary")][position()=1]')
109 |
110 | def _set_private_key(self):
111 | if self.private_key is not None: # Change account.
112 | self.driver_action.clickable('//button[@data-testid="popover-close"]')
113 | self.driver_action.clickable( # Click on the menu icon.
114 | '//*[@class="account-menu__icon"][position()=1]')
115 | self.driver_action.clickable('//*[contains(@class, "account-menu__item--'
116 | 'clickable")][position()=2]')
117 | self.driver_action.send_keys( # Input the private key.
118 | '//*[@id="private-key-box"]', self.private_key)
119 | self.driver_action.clickable('//*[contains(@class, "btn-secondary")]'
120 | '[position()=1]')
121 |
122 | def sign(self, contract: bool = True, page: int = 2) -> None:
123 | """Sign the MetaMask contract to login to OpenSea."""
124 | windows = self.driver_action.driver.window_handles # Opened windows.
125 | for _ in range(page): # "Next" and "Connect" buttons.
126 | self.driver_action.window_handles(2) # Switch to the MetaMask pop up tab.
127 | self.driver_action.clickable('//*[contains(@class, "btn-primary")]')
128 | if contract:
129 | self.driver_action.wait_new_tab(windows)
130 | self.contract() # Sign the contract.
131 |
132 | def contract(self, new_contract: bool = False) -> None:
133 | """Sign a MetaMask contract to upload or confirm sale."""
134 | self.driver_action.window_handles(2) # Switch to the MetaMask pop up tab.
135 | if self.driver_action.window == 1 and new_contract: # GeckoDriver.
136 | self.driver_action.clickable('(//div[contains(@class, "signature") and '
137 | 'contains(@class, "scroll")])[position()=1]')
138 | self.driver_action.driver.execute_script( # Scroll down.
139 | 'window.scrollTo(0, document.body.scrollHeight);')
140 | # Click on the "Sign" button - Make a contract link.
141 | rule_first = 'contains(@class, "signature")'
142 | rule_second = 'contains(@class, "footer")'
143 | xpath = f'(//div[{rule_first} and {rule_second}])[position()=1]/button[2]'
144 | self.driver_action.clickable(xpath)
145 | if not self.driver_action.wait_popup_close():
146 | # Sign the contract a second time.
147 | self.contract()
148 | # Switch back to the OpenSea tab.
149 | self.driver_action.window_handles(1)
150 |
151 | def close(self) -> None:
152 | """Close the MetaMask popup."""
153 | if len(self.driver_action.driver.window_handles) > 2:
154 | try:
155 | self.driver_action.window_handles(2) # Switch to the MetaMask popup.
156 | self.driver.close() # Close the popup extension.
157 | self.driver_action.window_handles(1) # Switch back to OpenSea.
158 | except (Exception,):
159 | pass # Ignore the exception.
160 |
--------------------------------------------------------------------------------
/source/service/driver_action.py:
--------------------------------------------------------------------------------
1 | # pylint: skip-file
2 | """
3 | Driver Action Service
4 | """
5 | import os
6 | import time
7 | from datetime import datetime as dt
8 |
9 | from selenium.common.exceptions import TimeoutException as TE
10 | from selenium.webdriver.common.by import By
11 | from selenium.webdriver.common.keys import Keys
12 | from selenium.webdriver.support import expected_conditions as EC
13 | from selenium.webdriver.support.select import Select
14 | from selenium.webdriver.support.ui import WebDriverWait as WDW
15 |
16 |
17 | class DriverAction:
18 | """ This class is used to perform actions on the webdriver. """
19 | driver = None
20 | is_chrome = False
21 | is_firefox = False
22 |
23 | def __init__(self, driver, is_chrome, is_firefox):
24 | self.driver = driver
25 | self.is_chrome = is_chrome
26 | self.is_firefox = is_firefox
27 |
28 | def wait(self, seconds: int = 5) -> None:
29 | """ Wait for a certain amount of seconds. """
30 | try:
31 | WDW(self.driver, seconds).until(lambda _: True)
32 | except Exception as execption:
33 | print("error", execption)
34 |
35 | def set_setting_window(self, start_url="https://google.com"):
36 | """ Start the webdriver. """
37 | self.driver.get(start_url)
38 | # How many tabs
39 | handles = self.driver.window_handles
40 | size = len(handles)
41 | # Close other tabs if exist (Install extension)
42 | if size > 1:
43 | self.driver.close()
44 |
45 | def get_title(self):
46 | """ Get the title of the web page. """
47 | return self.driver.title if self.driver.title else ""
48 |
49 | def close_window(self) -> None:
50 | """ Try to close the webdriver. """
51 | try:
52 | self.driver.quit()
53 | except Exception as execption:
54 | print("error", execption)
55 |
56 | def is_exists_by_xpath(self, xpath, show_error=False):
57 | """ Check if an element exists by xpath. """
58 | try:
59 | # WDW(self.driver, 5).until(EC.presence_of_element_located((By.XPATH, xpath)))
60 | self.driver.find_element(By.XPATH, xpath)
61 | except Exception as execption:
62 | if show_error:
63 | print("error", execption)
64 | return False
65 | return True
66 |
67 | def switch_to_main_window(self):
68 | """Switch to the main tab."""
69 | self.window_handles(0)
70 |
71 | def switch_to_popup_window(self):
72 | """ Switch to the MetaMask pop up tab. """
73 | self.window_handles(1)
74 |
75 | def switch_to_window(self, index: int):
76 | """ Switch to the MetaMask pop up tab. """
77 | self.window_handles(index)
78 |
79 | def switch_to_iframe(self, iframe):
80 | """ Switch to the MetaMask pop up tab. """
81 | self.driver.switch_to.frame(iframe)
82 |
83 | def switch_to_default_content(self):
84 | """ Switch to the MetaMask pop up tab. """
85 | self.driver.switch_to.default_content()
86 |
87 | def check_diff_current_vs_url(self, url):
88 | """ Check if the current url is the same as the url. """
89 | try:
90 | return WDW(self.driver, 5).until(lambda _: self.driver.current_url != url)
91 | except TE:
92 | print("Timeout while waiting for the upload page.")
93 | return False
94 | except Exception as execption:
95 | print("error", execption)
96 | return False
97 |
98 | def quit(self) -> None:
99 | """Stop the webdriver."""
100 | try:
101 | self.driver.quit()
102 | except Exception as execption:
103 | print("error", execption)
104 |
105 | def clickable(self, element: str, show_error=False) -> None:
106 | """Click on an element if it's clickable using Selenium."""
107 | try:
108 | WDW(self.driver, 5).until(
109 | EC.element_to_be_clickable((By.XPATH, element))
110 | ).click()
111 | except Exception as execption:
112 | if show_error:
113 | print("error", execption)
114 | self.clickable_js(element, show_error)
115 |
116 | def clickable_js(self, element: str, show_error=False) -> None:
117 | """ Click on an element if it's clickable using JavaScript."""
118 | try:
119 | # JavaScript can bypass this.
120 | self.driver.execute_script("arguments[0].click();", self.visible(element))
121 | except Exception as execption:
122 | if show_error:
123 | print("error", execption)
124 |
125 | def visible(self, element: str):
126 | """ Check if an element is visible using Selenium. """
127 | try:
128 | return WDW(self.driver, 15).until(
129 | EC.visibility_of_element_located((By.XPATH, element))
130 | )
131 | except Exception as execption:
132 | print("error", execption)
133 | return False
134 |
135 | def find_by_tag(self, element: str):
136 | """ Find an element by tag name. """
137 | try:
138 | return self.driver.find_element(By.TAG_NAME, element)
139 | except Exception as execption:
140 | print("error", execption)
141 | return []
142 |
143 | def find_all_by_tag(self, element: str):
144 | """ Find all elements by tag name. """
145 | try:
146 | return WDW(self.driver, 15).until(
147 | lambda _: self.driver.find_elements(By.TAG_NAME, element)
148 | )
149 | except Exception as execption:
150 | print("error", execption)
151 | return []
152 |
153 | def send_keys(self, element: str, keys: str) -> None:
154 | """ Send keys to an element if it's visible using Selenium. """
155 | try:
156 | self.visible(element).send_keys(keys)
157 | except Exception as execption:
158 | print("error", execption)
159 | WDW(self.driver, 5).until(
160 | EC.presence_of_element_located((By.XPATH, element))
161 | ).send_keys(keys)
162 |
163 | def send_date(self, element: str, keys: str) -> None:
164 | """ Send a date (DD-MM-YYYY HH:MM) to a date input by clicking on it. """
165 | # GeckoDriver (Mozilla Firefox).
166 | if self.is_firefox:
167 | self.send_keys(element, '-'.join(reversed(keys.split('-'))) if '-' in keys else keys)
168 | # ChromeDriver (Google Chrome).
169 | if self.is_chrome:
170 | keys = keys.split('-') if '-' in keys else [keys]
171 | keys = [keys[1], keys[0], keys[2]] if len(keys) > 1 else keys
172 | for part in range(len(keys) - 1 if keys[len(keys) - 1] == str(
173 | dt.now().year) else len(keys)): # Number of clicks.
174 | self.clickable(element) # Click first on the element.
175 | self.send_keys(element, keys[part]) # Then send it the date.
176 |
177 | def clear_text(self, element, webdriver) -> None:
178 | """ Clear text from an input. """
179 | self.clickable(element) # Click on the element then clear its text.
180 | # Note: change with 'darwin' if it's not working on MacOS.
181 | control = Keys.COMMAND if os.name == "posix" else Keys.CONTROL
182 | # ChromeDriver (Google Chrome).
183 | if self.is_chrome:
184 | webdriver.ActionChains(self.driver) \
185 | .key_down(control) \
186 | .send_keys('a') \
187 | .key_up(control) \
188 | .perform()
189 | # GeckoDriver (Mozilla Firefox).
190 | if self.is_firefox:
191 | self.send_keys(element, (control, 'a'))
192 |
193 | def is_empty(self, element: str, data: str, value: str = '') -> bool:
194 | """ Check if data is empty and input its value. """
195 | if data != value: # Check if the data is not an empty string
196 | self.send_keys(element, data) # or a default value, and send it.
197 | return False
198 | return True
199 |
200 | def wait_new_tab(self, windows):
201 | """ Wait for the new tab. """
202 | WDW(self.driver, 10).until(
203 | lambda _: windows != self.driver.window_handles)
204 |
205 | def wait_popup_close(self):
206 | """ Wait for the popup to close. """
207 | try:
208 | # Wait until the popup is closed.
209 | WDW(self.driver, 10).until(EC.number_of_windows_to_be(2))
210 | return True
211 | except TE:
212 | return False
213 | except Exception as execption:
214 | print("error", execption)
215 | return False
216 |
217 | def window_handles(self, window_number: int) -> None:
218 | """ Check for window handles and wait until a specific tab is opened. """
219 | WDW(self.driver, 15).until(
220 | lambda _: len(self.driver.window_handles) > window_number
221 | )
222 | # Switch to the asked tab.
223 | self.driver.switch_to.window(self.driver.window_handles[window_number])
224 |
225 | def select_by_value(self, xpath: str, value: str) -> None:
226 | """ Select an option by its value. """
227 | try:
228 | # Selenium