├── VERSION ├── .dockerignore ├── .gitignore ├── please_sign.jpg ├── No-Trespassing.gif ├── example └── images │ ├── Botvid-19.png │ └── No-Trespassing.gif ├── supervisord.conf ├── docker-compose.yml ├── config.yml ├── .github └── workflows │ └── docker.yml ├── arm ├── helpers.py └── Dockerfile ├── helpers.py ├── workers ├── Webtop_Health_Statements.py ├── Amdocs_Health_Statements.py ├── Infogan_Health_Statements.py ├── Mashov_Health_Statements.py └── Health_Statements.py ├── README.md ├── Dockerfile ├── dockerbot.py └── License /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | screenshots 2 | forms-automation-cli.py 3 | forms-demo.py 4 | -------------------------------------------------------------------------------- /please_sign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/Botvid-19/HEAD/please_sign.jpg -------------------------------------------------------------------------------- /No-Trespassing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/Botvid-19/HEAD/No-Trespassing.gif -------------------------------------------------------------------------------- /example/images/Botvid-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/Botvid-19/HEAD/example/images/Botvid-19.png -------------------------------------------------------------------------------- /example/images/No-Trespassing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/Botvid-19/HEAD/example/images/No-Trespassing.gif -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:dockerbot] 5 | command=python /opt/dockerbot/dockerbot.py 6 | stdout_logfile=/var/log/dockerbot 7 | stdout_logfile_maxbytes = 0 8 | stderr_logfile=/dev/stderr 9 | stderr_logfile_maxbytes = 0 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | botvid: 5 | image: techblog/botvid-19 6 | container_name: botvid 7 | restart: always 8 | labels: 9 | - "com.ouroboros.enable=true" 10 | environment: 11 | - API_KEY= 12 | - ALLOWED_IDS= 13 | volumes: 14 | - ./botvid/config/:/opt/dockerbot/config 15 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | edu: 2 | USER_ID: 3 | USER_KEY: 4 | mashov: 5 | #Add Kids Block as needed 6 | #UNused Kid Block should be left empty or removed from file 7 | kid1: 8 | MASHOV_USER_ID_KID: 9 | MASHOV_USER_PWD_KID: 10 | MASHOV_SCHOOL_ID_KID: 11 | kid2: 12 | MASHOV_USER_ID_KID: 13 | MASHOV_USER_PWD_KID: 14 | MASHOV_SCHOOL_ID_KID: 15 | infogan: 16 | BASE_URL: 17 | PARENT_NAME: 18 | PARENT_ID: 19 | KID_NAME: 20 | KID_ID: 21 | webtop: 22 | USER_ID: 23 | USER_KEY: 24 | amdocs: 25 | EMAIL: 26 | USER_ID: 27 | PASSWORD: -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: docker_builde 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v2 15 | - 16 | name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | - 19 | name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v1 21 | - 22 | name: Login to DockerHub 23 | uses: docker/login-action@v1 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_TOKEN }} 27 | 28 | - name: Get current date 29 | id: getDate 30 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 31 | 32 | - name: Get semantic version from file 33 | id: getSemver 34 | run: echo "::set-output name=semver::$(cat VERSION | tr -d ' \t\n\r' )" 35 | 36 | 37 | - 38 | name: Build and push 39 | uses: docker/build-push-action@v2 40 | with: 41 | context: . 42 | platforms: linux/amd64,linux/arm64,linux/arm/v7 43 | push: true 44 | tags: | 45 | techblog/botvid-19:latest 46 | techblog/botvid-19:${{ steps.getSemver.outputs.semver }} 47 | -------------------------------------------------------------------------------- /arm/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date 5 | from datetime import time 6 | from datetime import datetime 7 | import time 8 | import os 9 | from os import path 10 | from selenium.common.exceptions import InvalidSessionIdException 11 | from loguru import logger 12 | 13 | #### Setting ChromeOptions #### 14 | def GetBrowser(): 15 | options = webdriver.ChromeOptions() 16 | options.add_argument("-incognito") 17 | options.add_argument("--headless") 18 | options.add_argument("disable-gpu") 19 | options.add_argument("--no-sandbox") 20 | options.add_argument('--start-maximized') 21 | options.add_argument("--disable-dev-shm-usage") 22 | options.add_argument("--window-size=360,640") 23 | options.add_argument('--ignore-certificate-errors') 24 | browser = webdriver.Chrome(executable_path='/opt/chromedriver-83.0.4103.122/chromedriver', options=options) 25 | return browser 26 | 27 | ##### Screenshot for mobile view - like webtop ##### 28 | def mobile_screenshot(browser,Image): 29 | if path.exists(Image): 30 | os.remove(Image) 31 | logger.info(browser) 32 | browser.set_window_size(380, 660) #the trick 33 | time.sleep(2) 34 | browser.save_screenshot(Image) 35 | browser.close() 36 | 37 | #### Screenshot for regular view #### 38 | def fullpage_screenshot(browser,Image): 39 | if path.exists(Image): 40 | os.remove(Image) 41 | logger.info(browser) 42 | browser.set_window_size(800, 600) #the trick 43 | time.sleep(2) 44 | browser.save_screenshot(Image) 45 | 46 | 47 | #### Browser state logging #### 48 | def log_browser(browser): 49 | logger.debug(f"Opened page. Url: {browser.current_url}, size: {len(browser.page_source)}") 50 | 51 | 52 | 53 | def ping(browser, page): 54 | try: 55 | browser.get('https://bots.techblog.co.il/' + page + '.html') 56 | except: 57 | logger.info("Unable to ping") -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date 5 | from datetime import time 6 | from datetime import datetime 7 | import time 8 | import os 9 | from os import path 10 | from selenium.common.exceptions import InvalidSessionIdException 11 | from loguru import logger 12 | 13 | #### Setting ChromeOptions #### 14 | def GetBrowser(): 15 | options = webdriver.ChromeOptions() 16 | options.add_argument("-incognito") 17 | options.add_argument("--headless") 18 | options.add_argument("disable-gpu") 19 | options.add_argument("--no-sandbox") 20 | options.add_argument('--start-maximized') 21 | options.add_argument("--disable-dev-shm-usage") 22 | options.add_argument("--window-size=360,640") 23 | options.add_argument('--ignore-certificate-errors') 24 | browser = webdriver.Chrome(executable_path='/opt/chromedriver-86.0.4240.22/chromedriver', options=options) 25 | return browser 26 | 27 | ##### Screenshot for mobile view - like webtop ##### 28 | def mobile_screenshot(browser,Image): 29 | if path.exists(Image): 30 | os.remove(Image) 31 | logger.info(browser) 32 | browser.set_window_size(380, 900) #the trick 33 | time.sleep(2) 34 | browser.save_screenshot(Image) 35 | browser.close() 36 | 37 | #### Screenshot for regular view #### 38 | def fullpage_screenshot(browser,Image): 39 | if path.exists(Image): 40 | os.remove(Image) 41 | logger.info(browser) 42 | browser.set_window_size(800, 600) #the trick 43 | time.sleep(2) 44 | browser.save_screenshot(Image) 45 | 46 | 47 | #### Browser state logging #### 48 | def log_browser(browser): 49 | logger.debug(f"Opened page. Url: {browser.current_url}, size: {len(browser.page_source)}") 50 | 51 | 52 | 53 | def ping(browser, page): 54 | try: 55 | browser.get('https://bots.techblog.co.il/' + page + '.html') 56 | time.sleep(2) 57 | except: 58 | logger.info("Unable to ping") 59 | -------------------------------------------------------------------------------- /workers/Webtop_Health_Statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date, time, datetime 5 | from selenium.common.exceptions import InvalidSessionIdException 6 | import os, time, helpers 7 | from loguru import logger 8 | 9 | def sign(userCode, sitePassword, Image): 10 | try: 11 | browser = helpers.GetBrowser() 12 | logger.info("Starting process") 13 | 14 | try: 15 | helpers.ping(browser, 'infogan') 16 | except: 17 | logger.debug('Unable to ping') 18 | 19 | browser.get("https://www.webtop.co.il/mobilev2/?") 20 | helpers.log_browser(browser) 21 | time.sleep(1) 22 | 23 | browser.find_element_by_xpath('//*[@id="misradHachinuch"]').click() 24 | time.sleep(2) 25 | helpers.log_browser(browser) 26 | 27 | browser.get('https://lgn.edu.gov.il/nidp/wsfed/ep?id=EduCombinedAuthUidPwd&sid=0&option=credential&sid=0') 28 | time.sleep(2) 29 | helpers.log_browser(browser) 30 | browser.find_element_by_xpath('//*[@id="HIN_USERID"]').send_keys(str(userCode)) 31 | browser.find_element_by_xpath('//*[@id="Ecom_Password"]').send_keys(str(sitePassword)) 32 | time.sleep(1) 33 | logger.info('logged in!') 34 | browser.find_element_by_xpath('//*[@id="loginButton2"]').click() 35 | time.sleep(2) 36 | 37 | browser.get("https://www.webtop.co.il/mobilev2/corona.aspx") 38 | time.sleep(2) 39 | sign_btn=browser.find_element_by_xpath('//*[@id="viewData"]') 40 | if 'disabled' not in sign_btn.get_attribute('class').split(): 41 | logger.info("class: " + sign_btn.get_attribute('class')) 42 | sign_btn.click() 43 | time.sleep(1) 44 | browser.find_element_by_xpath('//*[@id="signForm"]').click() 45 | time.sleep(2) 46 | helpers.log_browser(browser) 47 | helpers.mobile_screenshot(browser,Image) 48 | return 1 49 | except Exception as ex: 50 | logger.error(str(ex)) 51 | return 0 52 | -------------------------------------------------------------------------------- /workers/Amdocs_Health_Statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date, datetime 5 | import time, os 6 | from selenium.common.exceptions import InvalidSessionIdException 7 | from loguru import logger 8 | import helpers 9 | 10 | def sign(email, user_id, password, Image): 11 | 12 | try: 13 | logger.info("Starting process") 14 | browser = helpers.GetBrowser() 15 | try: 16 | helpers.ping(browser, 'infogan') 17 | except: 18 | logger.debug('Unable to ping') 19 | 20 | browser.delete_all_cookies() 21 | browser.get("https://amdocsprodmain.service-now.com/one_portal?id=aop_sc_cat_item&sys_id=781f12f1db5cd8903780e1aa4b961903") 22 | helpers.log_browser(browser) 23 | 24 | time.sleep(4) 25 | 26 | # Email page 27 | xpath_email = browser.find_element_by_xpath('//*[@id="i0116"]') 28 | xpath_email_submit = browser.find_element_by_xpath('//*[@id="idSIButton9"]') 29 | 30 | xpath_email.send_keys(email) 31 | xpath_email_submit.click() 32 | 33 | time.sleep(4) 34 | 35 | # User and Password page 36 | xpath_user = browser.find_element_by_xpath('//*[@id="userNameInput"]') 37 | xpath_password = browser.find_element_by_xpath('//*[@id="passwordInput"]') 38 | xpath_sign_in = browser.find_element_by_xpath('//*[@id="submitButton"]') 39 | 40 | xpath_user.clear() 41 | xpath_user.send_keys("ntnet\\" + user_id) 42 | xpath_password.send_keys(password) 43 | xpath_sign_in.click() 44 | 45 | time.sleep(4) 46 | 47 | # Declaration 48 | xpath_approved = browser.find_element_by_xpath("//input[@name='u_approved']") 49 | xpath_submit = browser.find_element_by_xpath("//button[@name='submit']") 50 | 51 | xpath_approved.click() 52 | xpath_submit.click() 53 | 54 | helpers.log_browser(browser) 55 | helpers.fullpage_screenshot(browser,Image) 56 | 57 | return 1 58 | except Exception as ex: 59 | logger.error(str(ex)) 60 | return 0 61 | -------------------------------------------------------------------------------- /workers/Infogan_Health_Statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date, datetime 5 | import time, os 6 | from selenium.common.exceptions import InvalidSessionIdException 7 | from loguru import logger 8 | import helpers 9 | 10 | 11 | 12 | def sign(parentName, parentId, kidName, kidId, formUrl, Image): 13 | 14 | try: 15 | logger.info("Starting process") 16 | browser = helpers.GetBrowser() 17 | try: 18 | helpers.ping(browser, 'infogan') 19 | except: 20 | logger.debug('Unable to ping') 21 | 22 | browser.get(formUrl) 23 | helpers.log_browser(browser) 24 | #get needed elements 25 | kid_id = '//*[@id="form-field-email"]' 26 | kid_name = '//*[@id="form-field-name"]' 27 | parent_id = '//*[@id="form-field-field_4"]' 28 | parent_name = '//*[@id="form-field-field_3"]' 29 | form_date = '//*[@id="form-field-field_2"]' 30 | 31 | #Fill Date 32 | browser.find_element_by_xpath(form_date).send_keys(str(datetime.now().date())) 33 | # #UnFocus Date Fiels 34 | # browser.find_element_by_xpath(kid_id).click() 35 | #Fill Kid id 36 | browser.find_element_by_xpath(kid_id).send_keys(str(kidId)) 37 | #Fill Kid Name 38 | browser.find_element_by_xpath(kid_name).send_keys(str(kidName)) 39 | #Fill Parent ID 40 | browser.find_element_by_xpath(parent_id).send_keys(str(parentId)) 41 | #Fill Parent NAme 42 | browser.find_element_by_xpath(parent_name).send_keys(str(parentName)) 43 | 44 | #Set Checkbox 45 | browser.find_element_by_xpath('//*[@id="form-field-field_1-0"]').click() 46 | browser.find_element_by_xpath('//*[@id="form-field-field_1-1"]').click() 47 | browser.find_element_by_xpath('//*[@id="form-field-field_1-2"]').click() 48 | browser.find_element_by_xpath('//*[@id="form-field-field_5"]').click() 49 | 50 | #Send The Form 51 | browser.find_element_by_xpath('//*[@type="submit"]').submit() 52 | 53 | time.sleep(2) 54 | helpers.log_browser(browser) 55 | helpers.fullpage_screenshot(browser,Image) 56 | return 1 57 | except Exception as ex: 58 | logger.error(str(ex)) 59 | return 0 -------------------------------------------------------------------------------- /workers/Mashov_Health_Statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from selenium.webdriver.common.keys import Keys 5 | from datetime import date, time, datetime 6 | from selenium.common.exceptions import InvalidSessionIdException 7 | import os, time, helpers 8 | from loguru import logger 9 | 10 | 11 | def sign(user, password, schoolid, kidnum, Image): 12 | try: 13 | browser = helpers.GetBrowser() 14 | logger.info("Starting process") 15 | 16 | try: 17 | helpers.ping(browser, 'infogan') 18 | except: 19 | logger.debug('Unable to ping') 20 | logger.debug('----------------------------------------------------------------------') 21 | logger.info("Starting Mashov Sign process for kid number: " + kidnum) 22 | 23 | browser.get("https://web.mashov.info/students/login") 24 | time.sleep(3) 25 | 26 | #choose school: 27 | form_element_mashov_select_school = browser.find_element_by_xpath("//*[@id='mat-input-3']") 28 | form_element_mashov_user = browser.find_element_by_xpath("//*[@id='mat-input-0']") 29 | form_element_mashov_password = browser.find_element_by_xpath("//*[@id='mat-input-4']") 30 | form_element_mashov_login = browser.find_element_by_xpath("//*[@id='mat-tab-content-0-0']/div/div/button[1]") 31 | 32 | 33 | form_element_mashov_select_school.click() 34 | #form_element_mashov_select_school.send_keys(_convert(var_mashov_school_id)) # Moving to identify by school number instead of school name 35 | form_element_mashov_select_school.send_keys(schoolid) 36 | form_element_mashov_select_school.send_keys(Keys.ARROW_DOWN) 37 | form_element_mashov_select_school.send_keys(Keys.RETURN) 38 | 39 | form_element_mashov_user.click() 40 | form_element_mashov_user.send_keys(user) 41 | form_element_mashov_password.click() 42 | form_element_mashov_password.send_keys(password) 43 | form_element_mashov_login.click() 44 | time.sleep(3) 45 | 46 | logger.info(f"Logged in") 47 | 48 | form_element_mashov_select_daily_corona_report = browser.find_element_by_xpath("/html/body/mshv-root/mshv-main/mat-sidenav-container/mat-sidenav-content/mshv-student-covidsplash/mat-card/mat-card-content/div[3]/mat-card") 49 | form_element_mashov_select_daily_corona_report.click() 50 | time.sleep(2) 51 | 52 | form_element_mashov_submit_report = browser.find_element_by_xpath("/html/body/mshv-root/mshv-main/mat-sidenav-container/mat-sidenav-content/mshv-students-covid-clearance/mat-card/mat-card/mat-card-content[2]/mat-card-actions/button") 53 | if 'אני מצהיר' in form_element_mashov_submit_report.text: 54 | form_element_mashov_submit_report.click() 55 | 56 | 57 | logger.info(f"Submitted Report") 58 | time.sleep(1) 59 | 60 | helpers.log_browser(browser) 61 | helpers.fullpage_screenshot(browser,Image) 62 | 63 | logger.info(f"Screenshot Saved") 64 | logger.info("Finished Mashov Sign process for kid number: " + kidnum) 65 | logger.debug('----------------------------------------------------------------------') 66 | 67 | return 1 68 | except Exception as ex: 69 | logger.error(str(ex)) 70 | return 0 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Please :star: this repo if you find it useful* 2 | 3 |


4 | PayPal 5 |

6 | 7 | 8 | 9 | # Botvid-19 10 | 11 | Botvid-19 is a [Telepot](https://telepot.readthedocs.io/en/latest/) and selenium powered, easy to use Telegram bot for signing Covid-19 digital health statements. 12 | 13 | 14 | #### Credits: 15 | ======= 16 | 17 | - [Adam Russak](https://github.com/AdamRussak) for working with me on this project and writing the selenium part 18 | 19 | ## Usage 20 | 21 | ### supported platforms: 22 | * [edu](https://parents.education.gov.il) 23 | * [mashov](https://web.mashov.info/students/login) 24 | * [infogan](https://https://campaign.infogan.co.il/) 25 | * [webtop](https://www.webtop.co.il/mobilev2/?) 26 | 27 | #### docker-compose from hub 28 | ```yaml 29 | version: "3.7" 30 | 31 | services: 32 | botvid: 33 | image: techblog/botvid-19 34 | container_name: botvid 35 | restart: always 36 | labels: 37 | - "com.ouroboros.enable=true" 38 | environment: 39 | - API_KEY= 40 | - ALLOWED_IDS= 41 | volumes: 42 | - ./botvid/config/:/opt/dockerbot/config 43 | 44 | ``` 45 | 46 | Replace API_KEY with your bot token. if you do not have existing bot you can create one 47 | using the instruction in this article: 48 | [Bots: An introduction for developers](https://core.telegram.org/bots) 49 | 50 | In order to secure the bot and block unwanted calls from Unauthorized users add your allowd Id's with comma separated values into ALLOWED_IDS 51 | environmet. in order to get your id use @myidbot in telegram and send the /getid command. the result will be your ID. 52 | 53 | Run 54 | ``` 55 | docker-compose up -d 56 | ``` 57 | A config file will be created in ./botvid/config/config.yaml 58 | 59 | Please fill in the parameters in the file config.yml 60 | ``` 61 | edu: 62 | USER_ID: 63 | USER_KEY: 64 | mashov: 65 | #Add Kids Block as needed 66 | #UNused Kid Block should be left empty or removed from file 67 | kid1: 68 | MASHOV_USER_ID_KID: 69 | MASHOV_USER_PWD_KID: 70 | MASHOV_SCHOOL_ID_KID: 71 | kid2: 72 | MASHOV_USER_ID_KID: 73 | MASHOV_USER_PWD_KID: 74 | MASHOV_SCHOOL_ID_KID: 75 | infogan: 76 | BASE_URL: 77 | PARENT_NAME: 78 | PARENT_ID: 79 | KID_NAME: 80 | KID_ID: 81 | webtop: 82 | USER_ID: 83 | USER_KEY: 84 | ``` 85 | 86 | You may fill only the section that are relevant to you. 87 | 88 | Enter the bot in Telegram and run the relevant command: 89 | `/sign` - all configured commands 90 | `/sign_mashov` - only Mashov 91 | `/sign_infogan` - only InfoGan 92 | `/sign_webtop` - only WebTop 93 | `/?` or `/start` - show configured settings 94 | 95 | You will get the signed form after about 10 seconds. 96 | 97 | [![Telegram Bot Integration](https://raw.githubusercontent.com/t0mer/Botvid-19/master/example/images/Botvid-19.png "Telegram Bot Integration")](https://raw.githubusercontent.com/t0mer/Botvid-19/master/Botvid-19.png "Telegram Bot Integration") 98 | 99 | 100 | 101 | 102 | # Donation 103 |
104 | If you find this project helpful, you can give me a cup of coffee :) 105 | 106 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8CGLEHN2NDXDE) 107 | -------------------------------------------------------------------------------- /workers/Health_Statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | from datetime import date 5 | from datetime import time 6 | from datetime import datetime 7 | import time 8 | from argparse import ArgumentParser 9 | import os 10 | from selenium.common.exceptions import InvalidSessionIdException 11 | from loguru import logger 12 | import helpers 13 | 14 | 15 | def sign(userCode, sitePassword, Image): 16 | try: 17 | #### Starting Sign Proc #### 18 | logger.info("Starting process") 19 | logger.debug('-----------------------0000000000-----------------------------------------------') 20 | logger.debug(str(Image)) 21 | #### Initialize Browser #### 22 | browser = helpers.GetBrowser() 23 | 24 | try: 25 | helpers.ping(browser, 'edu') 26 | except: 27 | logger.debug('Unable to ping') 28 | 29 | browser.get("https://parents.education.gov.il/prhnet/parents/rights-obligations-regulations/health-statement-kindergarden") 30 | start = '/html/body/app-root/main/div/components-page/div/div[2]/section[1]/div/health-declaration/section[1]/div[1]/div[1]/div[4]/div/div/div/input' 31 | time.sleep(2) 32 | helpers.fullpage_screenshot(browser,Image) 33 | helpers.log_browser(browser) 34 | browser.find_element_by_xpath(start).click() 35 | time.sleep(2) 36 | helpers.log_browser(browser) 37 | browser.get('https://lgn.edu.gov.il/nidp/wsfed/ep?id=EduCombinedAuthUidPwd&sid=0&option=credential&sid=0') 38 | time.sleep(2) 39 | 40 | #### Logging In #### 41 | user = '//*[@id="HIN_USERID"]' 42 | siteAccess = '//*[@id="Ecom_Password"]' 43 | NextPhase = '//*[@id="loginButton2"]' 44 | browser.find_element_by_xpath(user).send_keys(userCode) 45 | browser.find_element_by_xpath(siteAccess).send_keys(sitePassword) 46 | browser.find_element_by_xpath(NextPhase).click() 47 | time.sleep(2) 48 | helpers.log_browser(browser) 49 | logger.info(f"Logged in") 50 | time.sleep(2) 51 | 52 | try: 53 | element = "//input[@value='מילוי הצהרת בריאות']" 54 | checkForButton = browser.find_elements_by_xpath(element) 55 | LenCheckForButton = len(checkForButton) 56 | logger.info(f"Starting sign... check buttons: {LenCheckForButton}") 57 | if LenCheckForButton == 0: 58 | logger.error("Not able to find the check buttons. Exit") 59 | helpers.fullpage_screenshot(browser, Image) 60 | 61 | elif LenCheckForButton > 0: 62 | for x in range(LenCheckForButton): 63 | logger.info(x + 1) 64 | browser.find_element_by_xpath(element).click() 65 | time.sleep(2) 66 | ToApprove = browser.find_elements_by_xpath("//input[@value='אישור']") 67 | 68 | for a in ToApprove: 69 | browser.execute_script("arguments[0].click()", a) 70 | time.sleep(2) 71 | except Exception as ex: 72 | logger.error(str(ex)) 73 | 74 | helpers.fullpage_screenshot(browser, Image) 75 | browser.close() 76 | return 1 77 | except Exception as ex: 78 | logger.info('#################################################################################################') 79 | logger.error(str(ex)) 80 | browser.close() 81 | return 0 82 | -------------------------------------------------------------------------------- /arm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM armhf/ubuntu 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV DEBCONF_NONINTERACTIVE_SEEN true 5 | ENV API_KEY "" 6 | ENV PYTHONIOENCODING=utf-8 7 | ENV LANG=C.UTF-8 8 | 9 | # Set timezone 10 | #RUN echo "Asia/Jerusalem" > /etc/timezone && \ 11 | # dpkg-reconfigure --frontend noninteractive tzdata 12 | 13 | # Create a default user 14 | RUN groupadd --system automation && \ 15 | useradd --system --create-home --gid automation --groups audio,video automation && \ 16 | mkdir --parents /home/automation/reports && \ 17 | chown --recursive automation:automation /home/automation 18 | 19 | # Update the repositories 20 | # Install dependencies 21 | # Install utilities 22 | # Install XVFB and TinyWM 23 | # Install fonts 24 | RUN apt-get -yqq update && \ 25 | apt-get -yqq install gnupg2 && \ 26 | apt-get -yqq install supervisor libnss3-dev && \ 27 | apt-get -yqq install curl unzip wget && \ 28 | apt-get -yqq install xvfb tinywm && \ 29 | apt update && \ 30 | apt full-upgrade -y && \ 31 | apt install chromium -y && \ 32 | rm -rf /var/lib/apt/lists/* 33 | 34 | # Install Chrome WebDriver 35 | #CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` 36 | RUN CHROMEDRIVER_VERSION=83.0.4103.122 && \ 37 | mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION && \ 38 | wget -O /tmp/chromedriver-v9.3.2-linux-armv7l.zip https://github.com/electron/electron/releases/download/v9.3.2/chromedriver-v9.3.2-linux-armv7l.zip && \ 39 | unzip -qq /tmp/chromedriver-v9.3.2-linux-armv7l.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION && \ 40 | rm /tmp/chromedriver-v9.3.2-linux-armv7l.zip && \ 41 | chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver && \ 42 | ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver 43 | 44 | RUN pip install selenium --no-cache-dir && \ 45 | pip install telepot --no-cache-dir && \ 46 | pip install pyyaml --no-cache-dir && \ 47 | pip install python-dotenv --no-cache-dir && \ 48 | pip install loguru --no-cache-dir 49 | 50 | RUN pip3 install --upgrade pip --no-cache-dir && \ 51 | pip3 install --upgrade setuptools --no-cache-dir && \ 52 | pip install selenium --no-cache-dir && \ 53 | pip install pyyaml --no-cache-dir && \ 54 | pip install flask --no-cache-dir && \ 55 | pip install flask_restful --no-cache-dir && \ 56 | pip install loguru --no-cache-dir && \ 57 | pip install cryptography==2.6.1 --no-cache-dir 58 | 59 | COPY config.yml /opt/dockerbot/config 60 | COPY config.yml /etc 61 | COPY workers/Health_Statements.py /opt/dockerbot 62 | COPY workers/Mashov_Health_Statements.py /opt/dockerbot 63 | COPY workers/Webtop_Health_Statements.py /opt/dockerbot 64 | COPY workers/Infogan_Health_Statements.py /opt/dockerbot 65 | COPY workers/Amdocs_Health_Statements.py /opt/dockerbot 66 | COPY arm/helpers.py /opt/dockerbot 67 | COPY dockerbot.py /opt/dockerbot 68 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 69 | COPY please_sign.jpg /opt/dockerbot 70 | COPY No-Trespassing.gif /opt/dockerbot 71 | 72 | VOLUME [ "/opt/config" ] 73 | 74 | RUN CHROMEDRIVER_VERSION=83.0.4103.122 && \ 75 | echo "export CHROME_VERSION=83.0.4103.122 " >> /root/.bashrc && \ 76 | echo 'export PATH=/opt/chromedriver-${CHROME_VERSION}:$PATH' >> /root/.bashrc 77 | 78 | # Default configuration 79 | ENV DISPLAY :20.0 80 | ENV SCREEN_GEOMETRY "1440x900x24" 81 | ENV CHROMEDRIVER_PORT 4444 82 | ENV CHROMEDRIVER_WHITELISTED_IPS "127.0.0.1" 83 | ENV CHROMEDRIVER_URL_BASE '' 84 | ENV CHROMEDRIVER_EXTRA_ARGS '' 85 | 86 | CMD ["/usr/bin/supervisord"] 87 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.14-rc-slim-bookworm 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV DEBCONF_NONINTERACTIVE_SEEN true 5 | ENV API_KEY "" 6 | ENV PYTHONIOENCODING=utf-8 7 | ENV LANG=C.UTF-8 8 | 9 | # Set timezone 10 | RUN echo "Asia/Jerusalem" > /etc/timezone && \ 11 | dpkg-reconfigure --frontend noninteractive tzdata 12 | 13 | # Create a default user 14 | RUN groupadd --system automation && \ 15 | useradd --system --create-home --gid automation --groups audio,video automation && \ 16 | mkdir --parents /home/automation/reports && \ 17 | chown --recursive automation:automation /home/automation 18 | 19 | # Update the repositories 20 | # Install dependencies 21 | # Install utilities 22 | # Install XVFB and TinyWM 23 | # Install fonts 24 | RUN apt-get -yqq update && \ 25 | apt-get -yqq install gnupg2 && \ 26 | apt-get -yqq install supervisor && \ 27 | apt-get -yqq install curl unzip && \ 28 | apt-get -yqq install xvfb tinywm && \ 29 | rm -rf /var/lib/apt/lists/* 30 | 31 | # Install Chrome WebDriver 32 | #CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` 33 | RUN CHROMEDRIVER_VERSION=86.0.4240.22 && \ 34 | mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION && \ 35 | curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip && \ 36 | unzip -qq /tmp/chromedriver_linux64.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION && \ 37 | rm /tmp/chromedriver_linux64.zip && \ 38 | chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver && \ 39 | ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver 40 | 41 | # Install Google Chrome 42 | RUN curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ 43 | echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list && \ 44 | apt-get -yqq update && \ 45 | apt-get -yqq install google-chrome-stable && \ 46 | rm -rf /var/lib/apt/lists/* 47 | 48 | # Default configuration 49 | ENV DISPLAY :20.0 50 | ENV SCREEN_GEOMETRY "1440x900x24" 51 | ENV CHROMEDRIVER_PORT 4444 52 | ENV CHROMEDRIVER_WHITELISTED_IPS "127.0.0.1" 53 | ENV CHROMEDRIVER_URL_BASE '' 54 | ENV CHROMEDRIVER_EXTRA_ARGS '' 55 | 56 | RUN pip install selenium --no-cache-dir && \ 57 | pip install telepot --no-cache-dir && \ 58 | pip install pyyaml --no-cache-dir && \ 59 | pip install python-dotenv --no-cache-dir && \ 60 | pip install loguru --no-cache-dir 61 | 62 | RUN mkdir -p /opt/dockerbot \ 63 | mkdir -p /opt/dockerbot/config \ 64 | mkdir -p /opt/dockerbot/images \ 65 | mkdir -p /var/log/supervisor 66 | 67 | COPY config.yml /opt/dockerbot/config 68 | COPY config.yml /etc 69 | COPY workers/Health_Statements.py /opt/dockerbot 70 | COPY workers/Mashov_Health_Statements.py /opt/dockerbot 71 | COPY workers/Webtop_Health_Statements.py /opt/dockerbot 72 | COPY workers/Infogan_Health_Statements.py /opt/dockerbot 73 | COPY workers/Amdocs_Health_Statements.py /opt/dockerbot 74 | COPY helpers.py /opt/dockerbot 75 | COPY dockerbot.py /opt/dockerbot 76 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 77 | COPY please_sign.jpg /opt/dockerbot 78 | COPY No-Trespassing.gif /opt/dockerbot 79 | 80 | VOLUME [ "/opt/config" ] 81 | 82 | RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \ 83 | echo "export CHROME_VERSION=86.0.4240.22" >> /root/.bashrc && \ 84 | echo 'export PATH=/opt/chromedriver-${CHROME_VERSION}:$PATH' >> /root/.bashrc 85 | 86 | CMD ["/usr/bin/supervisord"] 87 | #ENTRYPOINT ["/usr/bin/python", "/opt/dockerbot/dockerbot.py"] 88 | -------------------------------------------------------------------------------- /dockerbot.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | import time, re, random, datetime, telepot, os, sys 3 | from subprocess import call 4 | import subprocess, yaml, shutil 5 | from os import path 6 | from telepot.loop import MessageLoop 7 | from dotenv import load_dotenv 8 | 9 | configfile="/opt/dockerbot/config/config.yml" 10 | original_configfile = r'/etc/config.yml' 11 | 12 | 13 | def copyConfig(): 14 | if not os.path.exists(configfile): 15 | shutil.copyfile(original_configfile, configfile) 16 | 17 | 18 | copyConfig() 19 | 20 | # Load Configuration 21 | with open("/opt/dockerbot/config/config.yml", 'r') as stream: 22 | try: 23 | list = yaml.safe_load(stream) 24 | except yaml.YAMLError as exc: 25 | print(exc) 26 | 27 | # Vars For https://web.mashov.info 28 | v_MASHOV_NUMBER_OF_KIDS = len(list['mashov']) 29 | if v_MASHOV_NUMBER_OF_KIDS >= 1 and list['mashov']['kid1']['MASHOV_USER_ID_KID'] != None: 30 | v_MASHOV_NUMBER_OF_KIDS = v_MASHOV_NUMBER_OF_KIDS + 1 31 | 32 | def handle(msg): 33 | config_edu = 1 34 | config_mashov = 1 35 | message_id = str(msg['message_id']) 36 | chat_id = msg['chat']['id'] 37 | _command = command = str(msg['text']) 38 | logger.info("{message_id} Got msg: {command}") 39 | 40 | # Reject unauthorized requests 41 | if str(chat_id) not in os.getenv('ALLOWED_IDS'): 42 | bot.sendPhoto( 43 | chat_id, "https://github.com/t0mer/dockerbot/raw/master/No-Trespassing.gif") 44 | logger.error(f"[{message_id}] Chat id not allowed: {chat_id}") 45 | return 46 | #need to fix: so only active parts will show and not all options 47 | if command == '/?' or command == '/start': 48 | bot.sendMessage(chat_id, "List of available commands: ") 49 | if list['edu']['USER_ID'] and list['edu']['USER_KEY'] != None: 50 | bot.sendMessage( 51 | chat_id, "/sign_edu or /sign- This command start the sign process at https://parents.education.gov.il ") 52 | if v_MASHOV_NUMBER_OF_KIDS >= 1 and list['mashov']['kid1']['MASHOV_USER_ID_KID'] != None: 53 | bot.sendMessage( 54 | chat_id, "/sign_mashov - This command start the sign process at https://web.mashov.info/students/login ") 55 | if list['infogan']['BASE_URL'] and list['infogan']['KID_ID'] and list['infogan']['PARENT_NAME'] and list['infogan']['KID_NAME'] and list['infogan']['PARENT_ID'] != None: 56 | bot.sendMessage( 57 | chat_id, "/sign_infogan - This command start the sign process at https://www.infogan.co.il/ ") 58 | if list['webtop']['USER_ID'] and list['webtop']['USER_KEY'] != None: 59 | bot.sendMessage( 60 | chat_id, "/sign_webtop - This command start the sign process at https://www.webtop.co.il/v2/? ") 61 | if list['amdocs']['EMAIL'] and list['amdocs']['USER_ID'] and list['amdocs']['PASSWORD'] != None: 62 | bot.sendMessage( 63 | chat_id, "/sign_amdocs - This command start the sign process at Amdocs ") 64 | 65 | if command == '/sign_edu' or command == '/sign': 66 | if list['edu']['USER_ID'] and list['edu']['USER_KEY'] != None: 67 | Image = '/opt/dockerbot/images/edu_approval.png' 68 | try: 69 | bot.sendMessage( 70 | chat_id, "Starting Sign process at https://parents.education.gov.il") 71 | import Health_Statements 72 | if Health_Statements.sign(str(list['edu']['USER_ID']), list['edu']['USER_KEY'], Image) == 1: 73 | time.sleep(1) 74 | bot.sendPhoto(chat_id=chat_id, 75 | photo=open(str(Image), 'rb')) 76 | bot.sendMessage(chat_id, "Signed") 77 | logger.info( 78 | f"[{message_id}] Return result to command {command}. Result image path: {Image}") 79 | else: 80 | bot.sendMessage( 81 | chat_id, "Well, Somthing went wrong, please check the logs for more info") 82 | except Exception as ex: 83 | logger.exception( 84 | f"[{message_id}] Failed to handle command. Msg: {command}") 85 | bot.sendMessage(chat_id, f"ERROR: {str(ex)}") 86 | else: 87 | bot.sendMessage(chat_id, "edu NOT configured") 88 | 89 | if command == '/sign_infogan': 90 | if list['infogan']['BASE_URL'] and list['infogan']['KID_ID'] and list['infogan']['PARENT_NAME'] and list['infogan']['KID_NAME'] and list['infogan']['PARENT_ID'] != None: 91 | Image = '/opt/dockerbot/images/infogan_approval.png' 92 | try: 93 | bot.sendMessage( 94 | chat_id, "Starting Sign process at https://https://campaign.infogan.co.il/") 95 | import Infogan_Health_Statements 96 | if Infogan_Health_Statements.sign(list['infogan']['PARENT_NAME'], str(list['infogan']['PARENT_ID']), list['infogan']['KID_NAME'], str(list['infogan']['KID_ID']), list['infogan']['BASE_URL'], Image) == 1: 97 | bot.sendPhoto(chat_id=chat_id, photo=open(str(Image), 'rb')) 98 | time.sleep(1) 99 | os.remove(str(Image)) 100 | logger.info(f"[{message_id}] Return result to command {command}. Result image path: {Image}") 101 | bot.sendMessage(chat_id, "Signed") 102 | else: 103 | bot.sendMessage(chat_id, "Well, Somthing went wrong, please check the logs for more info") 104 | except Exception as ex: 105 | logger.exception( 106 | f"[{message_id}] Failed to handle command. Msg: {command}") 107 | bot.sendMessage(chat_id, f"ERROR: {str(ex)}") 108 | else: 109 | bot.sendMessage(chat_id, "infogan NOT configured") 110 | 111 | if command == '/sign_webtop': 112 | if list['webtop']['USER_ID'] and list['webtop']['USER_KEY'] != None: 113 | try: 114 | Image = '/opt/dockerbot/images/webtop_approval.png' 115 | bot.sendMessage( 116 | chat_id, "Starting Sign process at https://www.webtop.co.il/mobilev2/?") 117 | import Webtop_Health_Statements 118 | if Webtop_Health_Statements.sign(list['webtop']['USER_ID'], list['webtop']['USER_KEY'], Image) == 1: 119 | time.sleep(2) 120 | bot.sendPhoto(chat_id=chat_id, photo=open(str(Image), 'rb')) 121 | logger.info(f"[{message_id}] Return result to command {command}. Result image path: {Image}") 122 | bot.sendMessage(chat_id, "Signed") 123 | else: 124 | bot.sendMessage(chat_id, "Well, Somthing went wrong, please check the logs for more info") 125 | except Exception as ex: 126 | logger.exception(f"[{message_id}] Failed to handle command. Msg: {command}") 127 | bot.sendMessage(chat_id, f"ERROR: {str(ex)}") 128 | else: 129 | bot.sendMessage(chat_id, "webtop NOT configured") 130 | if command == '/sign_mashov': 131 | Image = '/opt/dockerbot/images/mashov_approval_' 132 | if v_MASHOV_NUMBER_OF_KIDS >= 1 and list['mashov']['kid1']['MASHOV_USER_ID_KID'] != None: 133 | try: 134 | import Mashov_Health_Statements 135 | if v_MASHOV_NUMBER_OF_KIDS >= 1 and list['mashov']['kid1']['MASHOV_USER_ID_KID'] != None: 136 | for Mashov_Kid_Number in range(1, v_MASHOV_NUMBER_OF_KIDS, 1): 137 | if list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_USER_ID_KID'] and list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_USER_PWD_KID'] and list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_SCHOOL_ID_KID'] != None: 138 | bot.sendMessage(chat_id,"Starting Sign process at https://web.mashov.info/students/login for Kid Number: " + str(Mashov_Kid_Number)) 139 | Prep_Switch_MASHOV_USER_DICT_ID_KID = list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_USER_ID_KID'] 140 | Prep_Switch_MASHOV_USER_DICT_ID_PWD = list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_USER_PWD_KID'] 141 | Prep_Switch_MASHOV_USER_DICT_ID_SCHOOL_ID = list['mashov']['kid'+str(Mashov_Kid_Number)]['MASHOV_SCHOOL_ID_KID'] 142 | Mashov_Health_Statements.sign(Prep_Switch_MASHOV_USER_DICT_ID_KID, Prep_Switch_MASHOV_USER_DICT_ID_PWD, Prep_Switch_MASHOV_USER_DICT_ID_SCHOOL_ID, str(Mashov_Kid_Number), Image + str(Mashov_Kid_Number) + ".png") 143 | else: 144 | bot.sendMessage(chat_id, "mashov NOT configured") 145 | config_mashov = 0 146 | 147 | for file in os.listdir("/opt/dockerbot/images"): 148 | if file.endswith(".png") and file.startswith("mashov"): 149 | Image = os.path.join("/opt/dockerbot/images", file) 150 | bot.sendPhoto(chat_id=chat_id, photo=open(str(Image), 'rb')) 151 | os.remove(str(Image)) 152 | logger.info(f"[{message_id}] Return result to command {command}. Result image path: {Image}") 153 | if config_mashov != 0: 154 | bot.sendMessage(chat_id, "Signed") 155 | except Exception as ex: 156 | logger.exception(f"[{message_id}] Failed to handle command. Msg: {command}") 157 | bot.sendMessage(chat_id, f"ERROR: {str(ex)}") 158 | else: 159 | bot.sendMessage(chat_id, "mashov NOT configured") 160 | 161 | if command == '/sign_amdocs': 162 | if list['amdocs']['EMAIL'] and list['amdocs']['USER_ID'] and list['amdocs']['PASSWORD'] != None: 163 | Image = '/opt/dockerbot/images/amdocs_approval.png' 164 | try: 165 | bot.sendMessage( 166 | chat_id, "Starting Sign process at Amdocs") 167 | import Amdocs_Health_Statements 168 | if Amdocs_Health_Statements.sign(str(list['amdocs']['EMAIL']), str(list['amdocs']['USER_ID']), str(list['amdocs']['PASSWORD']), Image) == 1: 169 | bot.sendPhoto(chat_id=chat_id, photo=open(str(Image), 'rb')) 170 | time.sleep(1) 171 | os.remove(str(Image)) 172 | logger.info(f"[{message_id}] Return result to command {command}. Result image path: {Image}") 173 | bot.sendMessage(chat_id, "Signed") 174 | else: 175 | bot.sendMessage(chat_id, "Well, Somthing went wrong, please check the logs for more info") 176 | except Exception as ex: 177 | logger.exception( 178 | f"[{message_id}] Failed to handle command. Msg: {command}") 179 | bot.sendMessage(chat_id, f"ERROR: {str(ex)}") 180 | else: 181 | bot.sendMessage(chat_id, "Amdocs NOT configured") 182 | 183 | msg = f"Done message handling: {command}" 184 | logger.info(f"[{message_id}] {msg}") 185 | 186 | bot = telepot.Bot(os.getenv('API_KEY')) 187 | MessageLoop(bot, handle).run_as_thread() 188 | logger.info('I am listening...') 189 | 190 | 191 | while 1: 192 | time.sleep(10) 193 | if os.path.isfile(configfile): 194 | continue 195 | else: 196 | copyConfig() 197 | logger.error("Recoverd Config to /opt/dockerbot/config/") -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------