├── README.md ├── _img.png ├── config.ini.example ├── embassy.py ├── esender.php ├── req_installer.bat └── visa.py /README.md: -------------------------------------------------------------------------------- 1 | # visa_rescheduler 2 | The visa_rescheduler is a bot for US VISA (usvisa-info.com) appointment rescheduling. This bot can help you reschedule your appointment to your desired time period. 3 | 4 | ## Prerequisites 5 | - Having a US VISA appointment scheduled already. 6 | - [Optional] API token from Pushover and/or a Sendgrid (for notifications)(You also can use the esender.php file in this repo as an email pusher on your website) 7 | 8 | ## Attention 9 | - Right now, there are lots of unsupported embassies in our repository. A list of supported embassies is presented in the 'embassy.py' file. 10 | - To add a new embassy (using English), you should find the embassy's "facility id." To do this, using google chrome, on the booking page of your account, right-click on the location section, then click "inspect." Then the right-hand window will be opened, highlighting the "select" item. You can find the "facility id" here and add this facility id in the 'embassy.py' file. There might be several facility ids for several different embassies. They can be added too. Please use the picture below as an illustration of the process. 11 | ![Finding Facility id](https://github.com/WTEngineer/ChatBot_US/blob/main/_img.png?raw=true) 12 | 13 | ## Initial Setup 14 | - Install Google Chrome [for install goto: https://www.google.com/chrome/] 15 | - Install Python v3 [for install goto: https://www.python.org/downloads/] 16 | - Install the required python packages: Just run the bat file in the Microsoft Windows. Or run the below commands: 17 | ``` 18 | pip install requests==2.27.1 19 | pip install selenium==4.2.0 20 | pip install webdriver-manager==3.7.0 21 | pip install sendgrid==6.9.7 22 | ``` 23 | 24 | ## How to use 25 | - Initial setup! 26 | - Edit information [config.ini.example file]. Then remove the ".example" from file name. 27 | - [Optional] Edit your push notification accounts information [config.ini.example file]. 28 | - [Optional] Edit your website push notification [config.ini.example and esender.php files]. 29 | - Run visa.py file, using `python3 visa.py` 30 | 31 | ## TODO 32 | - Make timing optimum. (There are lots of unanswered questions. How is the banning algorithm? How can we avoid it? etc.) 33 | - Adding a GUI (Based on PyQt) 34 | - Multi-account support (switching between accounts in Resting times) 35 | - Add a sound alert for different events. 36 | - Extend the embassies list. 37 | 38 | ## Acknowledgement 39 | Thanks to everyone who participated in this repo. Lots of people are using your excellent product without even appreciating you. 40 | -------------------------------------------------------------------------------- /_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTEngineer/ChatBot_US/25afe6499737c1073e6e62d7c179de867b7a25d2/_img.png -------------------------------------------------------------------------------- /config.ini.example: -------------------------------------------------------------------------------- 1 | [PERSONAL_INFO] 2 | ; Account and current appointment info from https://ais.usvisa-info.com 3 | USERNAME = account@gmail.com 4 | PASSWORD = account_pass 5 | ; Find SCHEDULE_ID in re-schedule page link: 6 | ; https://ais.usvisa-info.com/en-am/niv/schedule/{SCHEDULE_ID}/appointment 7 | SCHEDULE_ID = 99999999 8 | ; Target Period: 9 | PRIOD_START = 2023-03-20 10 | PRIOD_END = 2023-06-01 11 | ; Change "en-am-yer", based on your embassy Abbreviation in embassy.py list. 12 | YOUR_EMBASSY = en-am-yer 13 | 14 | [CHROMEDRIVER] 15 | ; Details for the script to control Chrome 16 | LOCAL_USE = True 17 | ; Optional: HUB_ADDRESS is mandatory only when LOCAL_USE = False 18 | HUB_ADDRESS = http://localhost:9515/wd/hub 19 | 20 | [NOTIFICATION] 21 | ; Get push notifications via https://pushover.net/ (optional) 22 | PUSHOVER_TOKEN = 23 | PUSHOVER_USER = 24 | ; Get email notifications via https://sendgrid.com/ (optional) 25 | SENDGRID_API_KEY = 26 | ; Get push notifications via PERSONAL WEBSITE http://yoursite.com (Optional) 27 | PERSONAL_SITE_USER = ********* 28 | PERSONAL_SITE_PASS = ********* 29 | PUSH_TARGET_EMAIL = notifyemail@gmail.com 30 | PERSONAL_PUSHER_URL = https://yoursite.com/api/esender.php 31 | 32 | [TIME] 33 | ; Time between retries/checks for available dates (seconds) 34 | RETRY_TIME_L_BOUND = 10 35 | RETRY_TIME_U_BOUND = 120 36 | ; Cooling down after WORK_LIMIT_TIME hours of work (Avoiding Ban)(hours) 37 | WORK_LIMIT_TIME = 1.5 38 | WORK_COOLDOWN_TIME = 2.25 39 | ; Temporary Banned (empty list): wait COOLDOWN_TIME (hours) 40 | BAN_COOLDOWN_TIME = 5 41 | -------------------------------------------------------------------------------- /embassy.py: -------------------------------------------------------------------------------- 1 | # Embassy List 2 | Embassies = { 3 | # [EMBASSY (COUNTRY CODE), FACILITY_ID (EMBASSY ID), "Continue in different languages"], 4 | "en-am-yer": ["en-am", 122, "Continue"], # English - Armenia - YEREVAN 5 | "es-co-bog": ["es-co", 25, "Continuar"], # Spanish - Colombia - Bogotá 6 | "en-ca-cal": ["en-ca", 89, "Continue"], # English - Canada - Calgary 7 | "en-ca-hal": ["en-ca", 90, "Continue"], # English - Canada - Halifax 8 | "en-ca-mon": ["en-ca", 91, "Continue"], # English - Canada - Montreal 9 | "en-ca-ott": ["en-ca", 92, "Continue"], # English - Canada - Ottawa 10 | "en-ca-que": ["en-ca", 93, "Continue"], # English - Canada - Quebec City 11 | "en-ca-tor": ["en-ca", 94, "Continue"], # English - Canada - Toronto 12 | "en-ca-van": ["en-ca", 95, "Continue"], # English - Canada - Vancouver 13 | } 14 | -------------------------------------------------------------------------------- /esender.php: -------------------------------------------------------------------------------- 1 | country_name."\nStateName: ".$data->state."\nCityName: ".$data->city."\n\nMessage:\n\n".$msg; 29 | // send email 30 | $headers = "From: pusher@your_website.com"; 31 | mail($email, "YOURSITE PUSH - ".$title, $msg, $headers); 32 | } 33 | } 34 | } 35 | ?> -------------------------------------------------------------------------------- /req_installer.bat: -------------------------------------------------------------------------------- 1 | pip install requests==2.27.1 2 | pip install selenium==4.2.0 3 | pip install webdriver-manager==3.7.0 4 | pip install sendgrid==6.9.7 -------------------------------------------------------------------------------- /visa.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import random 4 | import requests 5 | import configparser 6 | from datetime import datetime 7 | 8 | from selenium import webdriver 9 | from selenium.webdriver.chrome.service import Service 10 | from selenium.webdriver.support import expected_conditions as EC 11 | from selenium.webdriver.support.ui import WebDriverWait as Wait 12 | from selenium.webdriver.common.by import By 13 | from webdriver_manager.chrome import ChromeDriverManager 14 | 15 | from sendgrid import SendGridAPIClient 16 | from sendgrid.helpers.mail import Mail 17 | 18 | from embassy import * 19 | 20 | config = configparser.ConfigParser() 21 | config.read('config.ini') 22 | 23 | # Personal Info: 24 | # Account and current appointment info from https://ais.usvisa-info.com 25 | USERNAME = config['PERSONAL_INFO']['USERNAME'] 26 | PASSWORD = config['PERSONAL_INFO']['PASSWORD'] 27 | # Find SCHEDULE_ID in re-schedule page link: 28 | # https://ais.usvisa-info.com/en-am/niv/schedule/{SCHEDULE_ID}/appointment 29 | SCHEDULE_ID = config['PERSONAL_INFO']['SCHEDULE_ID'] 30 | # Target Period: 31 | PRIOD_START = config['PERSONAL_INFO']['PRIOD_START'] 32 | PRIOD_END = config['PERSONAL_INFO']['PRIOD_END'] 33 | # Embassy Section: 34 | YOUR_EMBASSY = config['PERSONAL_INFO']['YOUR_EMBASSY'] 35 | EMBASSY = Embassies[YOUR_EMBASSY][0] 36 | FACILITY_ID = Embassies[YOUR_EMBASSY][1] 37 | REGEX_CONTINUE = Embassies[YOUR_EMBASSY][2] 38 | 39 | # Notification: 40 | # Get email notifications via https://sendgrid.com/ (Optional) 41 | SENDGRID_API_KEY = config['NOTIFICATION']['SENDGRID_API_KEY'] 42 | # Get push notifications via https://pushover.net/ (Optional) 43 | PUSHOVER_TOKEN = config['NOTIFICATION']['PUSHOVER_TOKEN'] 44 | PUSHOVER_USER = config['NOTIFICATION']['PUSHOVER_USER'] 45 | # Get push notifications via PERSONAL WEBSITE http://yoursite.com (Optional) 46 | PERSONAL_SITE_USER = config['NOTIFICATION']['PERSONAL_SITE_USER'] 47 | PERSONAL_SITE_PASS = config['NOTIFICATION']['PERSONAL_SITE_PASS'] 48 | PUSH_TARGET_EMAIL = config['NOTIFICATION']['PUSH_TARGET_EMAIL'] 49 | PERSONAL_PUSHER_URL = config['NOTIFICATION']['PERSONAL_PUSHER_URL'] 50 | 51 | # Time Section: 52 | minute = 60 53 | hour = 60 * minute 54 | # Time between steps (interactions with forms) 55 | STEP_TIME = 0.5 56 | # Time between retries/checks for available dates (seconds) 57 | RETRY_TIME_L_BOUND = config['TIME'].getfloat('RETRY_TIME_L_BOUND') 58 | RETRY_TIME_U_BOUND = config['TIME'].getfloat('RETRY_TIME_U_BOUND') 59 | # Cooling down after WORK_LIMIT_TIME hours of work (Avoiding Ban) 60 | WORK_LIMIT_TIME = config['TIME'].getfloat('WORK_LIMIT_TIME') 61 | WORK_COOLDOWN_TIME = config['TIME'].getfloat('WORK_COOLDOWN_TIME') 62 | # Temporary Banned (empty list): wait COOLDOWN_TIME hours 63 | BAN_COOLDOWN_TIME = config['TIME'].getfloat('BAN_COOLDOWN_TIME') 64 | 65 | # CHROMEDRIVER 66 | # Details for the script to control Chrome 67 | LOCAL_USE = config['CHROMEDRIVER'].getboolean('LOCAL_USE') 68 | # Optional: HUB_ADDRESS is mandatory only when LOCAL_USE = False 69 | HUB_ADDRESS = config['CHROMEDRIVER']['HUB_ADDRESS'] 70 | 71 | SIGN_IN_LINK = f"https://ais.usvisa-info.com/{EMBASSY}/niv/users/sign_in" 72 | APPOINTMENT_URL = f"https://ais.usvisa-info.com/{EMBASSY}/niv/schedule/{SCHEDULE_ID}/appointment" 73 | DATE_URL = f"https://ais.usvisa-info.com/{EMBASSY}/niv/schedule/{SCHEDULE_ID}/appointment/days/{FACILITY_ID}.json?appointments[expedite]=false" 74 | TIME_URL = f"https://ais.usvisa-info.com/{EMBASSY}/niv/schedule/{SCHEDULE_ID}/appointment/times/{FACILITY_ID}.json?date=%s&appointments[expedite]=false" 75 | SIGN_OUT_LINK = f"https://ais.usvisa-info.com/{EMBASSY}/niv/users/sign_out" 76 | 77 | JS_SCRIPT = ("var req = new XMLHttpRequest();" 78 | f"req.open('GET', '%s', false);" 79 | "req.setRequestHeader('Accept', 'application/json, text/javascript, */*; q=0.01');" 80 | "req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');" 81 | f"req.setRequestHeader('Cookie', '_yatri_session=%s');" 82 | "req.send(null);" 83 | "return req.responseText;") 84 | 85 | def send_notification(title, msg): 86 | print(f"Sending notification!") 87 | if SENDGRID_API_KEY: 88 | message = Mail(from_email=USERNAME, to_emails=USERNAME, subject=msg, html_content=msg) 89 | try: 90 | sg = SendGridAPIClient(SENDGRID_API_KEY) 91 | response = sg.send(message) 92 | print(response.status_code) 93 | print(response.body) 94 | print(response.headers) 95 | except Exception as e: 96 | print(e.message) 97 | if PUSHOVER_TOKEN: 98 | url = "https://api.pushover.net/1/messages.json" 99 | data = { 100 | "token": PUSHOVER_TOKEN, 101 | "user": PUSHOVER_USER, 102 | "message": msg 103 | } 104 | requests.post(url, data) 105 | if PERSONAL_SITE_USER: 106 | url = PERSONAL_PUSHER_URL 107 | data = { 108 | "title": "VISA - " + str(title), 109 | "user": PERSONAL_SITE_USER, 110 | "pass": PERSONAL_SITE_PASS, 111 | "email": PUSH_TARGET_EMAIL, 112 | "msg": msg, 113 | } 114 | requests.post(url, data) 115 | 116 | 117 | def auto_action(label, find_by, el_type, action, value, sleep_time=0): 118 | print("\t"+ label +":", end="") 119 | # Find Element By 120 | match find_by.lower(): 121 | case 'id': 122 | item = driver.find_element(By.ID, el_type) 123 | case 'name': 124 | item = driver.find_element(By.NAME, el_type) 125 | case 'class': 126 | item = driver.find_element(By.CLASS_NAME, el_type) 127 | case 'xpath': 128 | item = driver.find_element(By.XPATH, el_type) 129 | case _: 130 | return 0 131 | # Do Action: 132 | match action.lower(): 133 | case 'send': 134 | item.send_keys(value) 135 | case 'click': 136 | item.click() 137 | case _: 138 | return 0 139 | print("\t\tCheck!") 140 | if sleep_time: 141 | time.sleep(sleep_time) 142 | 143 | 144 | def start_process(): 145 | # Bypass reCAPTCHA 146 | driver.get(SIGN_IN_LINK) 147 | time.sleep(STEP_TIME) 148 | Wait(driver, 60).until(EC.presence_of_element_located((By.NAME, "commit"))) 149 | auto_action("Click bounce", "xpath", '//a[@class="down-arrow bounce"]', "click", "", STEP_TIME) 150 | auto_action("Email", "id", "user_email", "send", USERNAME, STEP_TIME) 151 | auto_action("Password", "id", "user_password", "send", PASSWORD, STEP_TIME) 152 | auto_action("Privacy", "class", "icheckbox", "click", "", STEP_TIME) 153 | auto_action("Enter Panel", "name", "commit", "click", "", STEP_TIME) 154 | Wait(driver, 60).until(EC.presence_of_element_located((By.XPATH, "//a[contains(text(), '" + REGEX_CONTINUE + "')]"))) 155 | print("\n\tlogin successful!\n") 156 | 157 | def reschedule(date): 158 | time = get_time(date) 159 | driver.get(APPOINTMENT_URL) 160 | headers = { 161 | "User-Agent": driver.execute_script("return navigator.userAgent;"), 162 | "Referer": APPOINTMENT_URL, 163 | "Cookie": "_yatri_session=" + driver.get_cookie("_yatri_session")["value"] 164 | } 165 | data = { 166 | "utf8": driver.find_element(by=By.NAME, value='utf8').get_attribute('value'), 167 | "authenticity_token": driver.find_element(by=By.NAME, value='authenticity_token').get_attribute('value'), 168 | "confirmed_limit_message": driver.find_element(by=By.NAME, value='confirmed_limit_message').get_attribute('value'), 169 | "use_consulate_appointment_capacity": driver.find_element(by=By.NAME, value='use_consulate_appointment_capacity').get_attribute('value'), 170 | "appointments[consulate_appointment][facility_id]": FACILITY_ID, 171 | "appointments[consulate_appointment][date]": date, 172 | "appointments[consulate_appointment][time]": time, 173 | } 174 | r = requests.post(APPOINTMENT_URL, headers=headers, data=data) 175 | if(r.text.find('Successfully Scheduled') != -1): 176 | title = "SUCCESS" 177 | msg = f"Rescheduled Successfully! {date} {time}" 178 | else: 179 | title = "FAIL" 180 | msg = f"Reschedule Failed!!! {date} {time}" 181 | return [title, msg] 182 | 183 | 184 | def get_date(): 185 | # Requesting to get the whole available dates 186 | session = driver.get_cookie("_yatri_session")["value"] 187 | script = JS_SCRIPT % (str(DATE_URL), session) 188 | content = driver.execute_script(script) 189 | return json.loads(content) 190 | 191 | def get_time(date): 192 | time_url = TIME_URL % date 193 | session = driver.get_cookie("_yatri_session")["value"] 194 | script = JS_SCRIPT % (str(time_url), session) 195 | content = driver.execute_script(script) 196 | data = json.loads(content) 197 | time = data.get("available_times")[-1] 198 | print(f"Got time successfully! {date} {time}") 199 | return time 200 | 201 | 202 | def is_logged_in(): 203 | content = driver.page_source 204 | if(content.find("error") != -1): 205 | return False 206 | return True 207 | 208 | 209 | def get_available_date(dates): 210 | # Evaluation of different available dates 211 | def is_in_period(date, PSD, PED): 212 | new_date = datetime.strptime(date, "%Y-%m-%d") 213 | result = ( PED > new_date and new_date > PSD ) 214 | # print(f'{new_date.date()} : {result}', end=", ") 215 | return result 216 | 217 | PED = datetime.strptime(PRIOD_END, "%Y-%m-%d") 218 | PSD = datetime.strptime(PRIOD_START, "%Y-%m-%d") 219 | for d in dates: 220 | date = d.get('date') 221 | if is_in_period(date, PSD, PED): 222 | return date 223 | print(f"\n\nNo available dates between ({PSD.date()}) and ({PED.date()})!") 224 | 225 | 226 | def info_logger(file_path, log): 227 | # file_path: e.g. "log.txt" 228 | with open(file_path, "a") as file: 229 | file.write(str(datetime.now().time()) + ":\n" + log + "\n") 230 | 231 | 232 | if LOCAL_USE: 233 | driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 234 | else: 235 | driver = webdriver.Remote(command_executor=HUB_ADDRESS, options=webdriver.ChromeOptions()) 236 | 237 | 238 | if __name__ == "__main__": 239 | first_loop = True 240 | while 1: 241 | LOG_FILE_NAME = "log_" + str(datetime.now().date()) + ".txt" 242 | if first_loop: 243 | t0 = time.time() 244 | total_time = 0 245 | Req_count = 0 246 | start_process() 247 | first_loop = False 248 | Req_count += 1 249 | try: 250 | msg = "-" * 60 + f"\nRequest count: {Req_count}, Log time: {datetime.today()}\n" 251 | print(msg) 252 | info_logger(LOG_FILE_NAME, msg) 253 | dates = get_date() 254 | if not dates: 255 | # Ban Situation 256 | msg = f"List is empty, Probabely banned!\n\tSleep for {BAN_COOLDOWN_TIME} hours!\n" 257 | print(msg) 258 | info_logger(LOG_FILE_NAME, msg) 259 | send_notification("BAN", msg) 260 | driver.get(SIGN_OUT_LINK) 261 | time.sleep(BAN_COOLDOWN_TIME * hour) 262 | first_loop = True 263 | else: 264 | # Print Available dates: 265 | msg = "" 266 | for d in dates: 267 | msg = msg + "%s" % (d.get('date')) + ", " 268 | msg = "Available dates:\n"+ msg 269 | print(msg) 270 | info_logger(LOG_FILE_NAME, msg) 271 | date = get_available_date(dates) 272 | if date: 273 | # A good date to schedule for 274 | END_MSG_TITLE, msg = reschedule(date) 275 | break 276 | RETRY_WAIT_TIME = random.randint(RETRY_TIME_L_BOUND, RETRY_TIME_U_BOUND) 277 | t1 = time.time() 278 | total_time = t1 - t0 279 | msg = "\nWorking Time: ~ {:.2f} minutes".format(total_time/minute) 280 | print(msg) 281 | info_logger(LOG_FILE_NAME, msg) 282 | if total_time > WORK_LIMIT_TIME * hour: 283 | # Let program rest a little 284 | send_notification("REST", f"Break-time after {WORK_LIMIT_TIME} hours | Repeated {Req_count} times") 285 | driver.get(SIGN_OUT_LINK) 286 | time.sleep(WORK_COOLDOWN_TIME * hour) 287 | first_loop = True 288 | else: 289 | msg = "Retry Wait Time: "+ str(RETRY_WAIT_TIME)+ " seconds" 290 | print(msg) 291 | info_logger(LOG_FILE_NAME, msg) 292 | time.sleep(RETRY_WAIT_TIME) 293 | except: 294 | # Exception Occured 295 | msg = f"Break the loop after exception!\n" 296 | END_MSG_TITLE = "EXCEPTION" 297 | break 298 | 299 | print(msg) 300 | info_logger(LOG_FILE_NAME, msg) 301 | send_notification(END_MSG_TITLE, msg) 302 | driver.get(SIGN_OUT_LINK) 303 | driver.stop_client() 304 | driver.quit() 305 | --------------------------------------------------------------------------------