├── requirements.txt ├── config.py ├── .gitignore ├── web ├── main.py ├── service.py ├── models.py └── router.py ├── queue_bot.py ├── core ├── image_processing.py └── queue_checker.py ├── README.md └── telebot.py /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZotovaElena/kdmid_queue_checker/HEAD/requirements.txt -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | TESSERACT_PATH = r'C:\Program Files\Tesseract-OCR\tesseract.exe' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | .ipynb_checkpoints 5 | *.log 6 | .vscode/ 7 | *screenshot 8 | success_files 9 | img 10 | logs -------------------------------------------------------------------------------- /web/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from web import router 4 | 5 | def create_app(): 6 | app = FastAPI() 7 | app.include_router(router.api) 8 | 9 | # https://github.com/tiangolo/fastapi/issues/1921 10 | @app.get('/') 11 | def root(): 12 | return {'message': 'Main Page for routing'} 13 | 14 | return app 15 | 16 | if __name__ == '__main__': 17 | import uvicorn 18 | uvicorn.run(create_app(), host='0.0.0.0', port=8000) -------------------------------------------------------------------------------- /web/service.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import argparse 4 | import threading 5 | import json 6 | 7 | from core.queue_checker import QueueChecker 8 | 9 | 10 | # kdmid_subdomain = 'madrid' 11 | # order_id = '130238' 12 | # code = 'CD9E05C1' 13 | 14 | # kdmid_subdomain = 'barcelona' 15 | # order_id = '205619' 16 | # code = '8367159E' 17 | 18 | # every_hours = 3 19 | 20 | # kdmid_subdomain = 'madrid' 21 | # order_id = '151321' 22 | # code = '5CCF3A7C' 23 | 24 | # 'madrid', '151321', '5CCF3A7C' 25 | 26 | checker = QueueChecker() 27 | 28 | def run_check_queue(kdmid_subdomain, order_id, code, every_hours=1): 29 | success_file = order_id+"_"+code+"_success.json" 30 | error_file = order_id+"_"+code+"_error.json" 31 | 32 | while True: 33 | message, status = checker.check_queue(kdmid_subdomain, order_id, code) 34 | 35 | if os.path.isfile(success_file) or os.path.isfile(error_file): 36 | break 37 | 38 | time.sleep(every_hours*3600) # Pause for every_hours * hour before the next check 39 | 40 | 41 | 42 | 43 | # run_check_queue(kdmid_subdomain, order_id, code) 44 | -------------------------------------------------------------------------------- /web/models.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Dict, List, Optional, Any 3 | 4 | from pydantic import BaseModel, validator, Field 5 | 6 | 7 | class CheckingExpectModel(BaseModel): 8 | 9 | kdmid_subdomain : str = Field(..., example='madrid') 10 | order_id : str = Field(..., example='130238') 11 | code : str = Field(..., example='CD9E05C1') 12 | every_hours : int = Field(..., example=2) 13 | 14 | __annotations__ = { 15 | "kdmid_subdomain": str, 16 | "order_id": str, 17 | "code": str, 18 | "every_hours": int 19 | } 20 | class Config: 21 | schema_extra = { 22 | "example": { 23 | "kdmid_subdomain": "madrid", 24 | "order_id": "130238", 25 | "code": "CD9E05C1", 26 | "every_hours": 2 27 | } 28 | } 29 | 30 | class CheckingResponseModel(BaseModel): 31 | status: str = Field(..., example='success') 32 | message: str = Field(..., example='The queue is over') 33 | __annotations__ = { 34 | "status": str, 35 | "message": str 36 | } 37 | class Config: 38 | schema_extra = { 39 | "example": { 40 | "status": "success", 41 | "message": "The queue is over" 42 | } 43 | } 44 | 45 | class ErrorResponseModel(BaseModel): 46 | status: str = Field(..., example='success') 47 | message: str = Field(..., example='The queue is over') 48 | __annotations__ = { 49 | "status": str, 50 | "message": str 51 | } 52 | class Config: 53 | schema_extra = { 54 | "example": { 55 | "status": "success", 56 | "message": "The queue is over" 57 | } 58 | } -------------------------------------------------------------------------------- /queue_bot.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import argparse 4 | 5 | from queue_class import QueueChecker 6 | 7 | # kdmid_subdomain = 'madrid' 8 | # order_id = '130238' 9 | # code = 'CD9E05C1' 10 | 11 | # https://warsaw.kdmid.ru/queue/OrderInfo.aspx?id=85914&cd=824D737D 12 | 13 | kdmid_subdomain = 'warsaw' 14 | order_id = '85914' 15 | code = '824D737D' 16 | 17 | kdmid_subdomain = 'barcelona' 18 | order_id = '205619' 19 | code = '8367159E' 20 | 21 | 22 | every_hours = 3 23 | 24 | def run(queue_checker, every_hours): 25 | success = False 26 | while not success: 27 | if not os.path.isfile(queue_checker.order_id+"_"+queue_checker.code+"_success.txt"): 28 | queue_checker.check_queue() 29 | time.sleep(every_hours*3600) 30 | else: 31 | print('file exists, exiting') 32 | success = True 33 | 34 | queue_checker = QueueChecker(kdmid_subdomain, order_id, code) 35 | 36 | run(queue_checker, 3) 37 | 38 | 39 | 40 | # if __name__ == '__main__': 41 | # 42 | # parser = argparse.ArgumentParser(description='Parameters for checking') 43 | 44 | # parser.add_argument('--subdomain', 45 | # type=str, required=True, 46 | # help='The city where the consulate is situated') 47 | 48 | # parser.add_argument('--order_id', 49 | # type=str, required=True, 50 | # help='Номер заявки') 51 | 52 | # parser.add_argument('--code', 53 | # type=str, required=True, 54 | # help='Защитный код') 55 | 56 | # parser.add_argument('--every_hours', 57 | # type=int, default=3, 58 | # help='Every n hours to check the queue, default 2') 59 | 60 | # args = parser.parse_args() 61 | # 62 | # queue_checker = QueueChecker(args.subdomain, args.order_id, args.code) 63 | # 64 | # run(queue_checker, args.every_hours) 65 | -------------------------------------------------------------------------------- /core/image_processing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | # functions to process captcha images 5 | def bfs(visited, queue, array, node): 6 | # I make BFS itterative instead of recursive 7 | def getNeighboor(array, node): 8 | neighboors = [] 9 | if node[0]+10: 13 | if array[node[0]-1,node[1]] == 0: 14 | neighboors.append((node[0]-1,node[1])) 15 | if node[1]+10: 19 | if array[node[0],node[1]-1] == 0: 20 | neighboors.append((node[0],node[1]-1)) 21 | return neighboors 22 | 23 | queue.append(node) 24 | visited.add(node) 25 | 26 | while queue: 27 | current_node = queue.pop(0) 28 | for neighboor in getNeighboor(array, current_node): 29 | if neighboor not in visited: 30 | visited.add(neighboor) 31 | queue.append(neighboor) 32 | 33 | def removeIsland(img_arr, threshold): 34 | # !important: the black pixel is 0 and white pixel is 1 35 | while 0 in img_arr: 36 | x,y = np.where(img_arr == 0) 37 | point = (x[0],y[0]) 38 | visited = set() 39 | queue = [] 40 | bfs(visited, queue, img_arr, point) 41 | 42 | if len(visited) <= threshold: 43 | for i in visited: 44 | img_arr[i[0],i[1]] = 1 45 | else: 46 | # if the cluster is larger than threshold (i.e is the text), 47 | # we convert it to a temporary value of 2 to mark that we 48 | # have visited it. 49 | for i in visited: 50 | img_arr[i[0],i[1]] = 2 51 | 52 | img_arr = np.where(img_arr==2, 0, img_arr) 53 | return img_arr 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KDMID Queue Checker Bot 2 | 3 | Automatically checks the status of a queue after appointment intent 4 | 5 | If you are in the queue waiting a free slot for an appointment in the Russian consulate, you must check the status of the queue at least once a day, 6 | which means enter the page with your order number and code and see if free slots are available. Once you stop entering the page daily, they remove your order from the queue and you have to start the process once again. 7 | 8 | 9 | This bot is designed to make the iterative checking process automatic and get the first nearest timeslot. 10 | If success, you will get an email from the consulate with the information about your appointment. 11 | 12 | ## Requirements 13 | 14 | *Tesseract* 15 | 16 | Tesseract OCR is used to recognize captcha digits. It should be installed on the machine. For Windows, see the installation instructons here https://github.com/UB-Mannheim/tesseract/wiki 17 | Then in _config.py_ file indicate the path to Tesseract. 18 | 19 | *Chrome* 20 | 21 | *Python 3.11* 22 | 23 | The script was depeloped and tested in Windows Anaconda environment for the consulate in Madrid 24 | 25 | ``` 26 | git clone https://github.com/ZotovaElena/kdmid_queue_checker.git 27 | 28 | ``` 29 | 30 | - install requirements in conda or pip virtual environment 31 | 32 | - execute the following command, where: 33 | 34 | *--subdomain* is the city of the consulate 35 | 36 | *--order_id* is the number of the order assigned when you applied for the appointment for the first time (номер заявки) 37 | 38 | *--code* is the security code of the order (защитный код) 39 | 40 | *--every_hour* how often the bot will repeat the check: minimal interval is 1 hour, default is 2 hours. 41 | It is not recommended to check the page too often not to generate suspisious behaviour. 42 | 43 | 44 | ``` 45 | python queue_bot.py --subdomain madrid --order_id 123610 --code 7AE8EFCC --every_hours 3 46 | ``` 47 | 48 | - execute in background mode: 49 | 50 | ``` 51 | python queue_bot.py --subdomain madrid --order_id 123610 --code 7AE8EFCC --every_hours 3 > output.txt & 52 | ``` 53 | 54 | The logs are saved in queue.log 55 | 56 | ### TODO 57 | 58 | - Option to send info about existing appointments without taking it. 59 | 60 | - User Interface 61 | 62 | - Telegram bot 63 | -------------------------------------------------------------------------------- /web/router.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import json 4 | import os 5 | import time 6 | from http import HTTPStatus 7 | import threading 8 | 9 | from fastapi import APIRouter, HTTPException 10 | from fastapi import BackgroundTasks 11 | from fastapi import Depends 12 | from typing import Dict, Any, Union 13 | 14 | from web.models import ( 15 | CheckingExpectModel, CheckingResponseModel, ErrorResponseModel 16 | ) 17 | # from web.service import run_check_queue 18 | from core.queue_checker import QueueChecker 19 | 20 | logging.basicConfig(level=logging.INFO, stream=sys.stdout) 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | def load_json_file(filename): 25 | with open(filename, 'r', encoding="utf-8") as f: 26 | data = json.load(f) 27 | return data 28 | 29 | checker = QueueChecker() 30 | 31 | class SharedState: 32 | def __init__(self): 33 | self.results: Dict[str, Any] = {} 34 | 35 | def run_check_queue(kdmid_subdomain, order_id, code, state: SharedState, every_hours=1): 36 | print('run_check_queue called') 37 | success_file = order_id + "_" + code + "_success.json" 38 | error_file = order_id + "_" + code + "_error.json" 39 | 40 | while True: 41 | checker.check_queue(kdmid_subdomain, order_id, code) 42 | 43 | if os.path.isfile(success_file): 44 | with open(success_file, 'r') as f: 45 | state.results[order_id] = json.load(f) 46 | elif os.path.isfile(error_file): 47 | with open(error_file, 'r') as f: 48 | state.results[order_id] = json.load(f) 49 | print(state.results[order_id]) 50 | break # Stop the iteration when the error file is found 51 | 52 | time.sleep(every_hours*1) 53 | 54 | 55 | def get_shared_state() -> SharedState: 56 | return SharedState() 57 | 58 | 59 | api = APIRouter( 60 | prefix='/queue_checker', 61 | tags=['KDMID Queue Checker'], 62 | responses={ 63 | 404: {"description": "Not found"} 64 | } 65 | ) 66 | 67 | 68 | @api.post( 69 | '/check', 70 | response_model=Union[CheckingResponseModel, ErrorResponseModel], 71 | status_code=200, 72 | responses={ 73 | 404: {"description": "Not found"} 74 | } 75 | ) 76 | def do_checking(background_tasks: BackgroundTasks, item: CheckingExpectModel, state: SharedState = Depends(get_shared_state)): 77 | print('do checking called') 78 | background_tasks.add_task(run_check_queue, item.kdmid_subdomain, item.order_id, item.code, state, item.every_hours) 79 | # Wait for the background task to finish 80 | if item.order_id in state.results: 81 | return state.results[item.order_id] -------------------------------------------------------------------------------- /telebot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | import os 4 | 5 | from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update 6 | from telegram.ext import ( 7 | Application, 8 | CommandHandler, 9 | ContextTypes, 10 | ConversationHandler, 11 | MessageHandler, 12 | filters, 13 | ) 14 | 15 | from core.queue_checker import QueueChecker 16 | 17 | logging.basicConfig( 18 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 19 | level=logging.INFO 20 | ) 21 | 22 | TOKEN = 'YOUR_TOKEN' 23 | 24 | # set higher logging level for httpx to avoid all GET and POST requests being logged 25 | logging.getLogger("httpx").setLevel(logging.WARNING) 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | INFO, STATUS = range(2) 30 | 31 | 32 | async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> str: 33 | """Starts the conversation and asks the user about their gender.""" 34 | 35 | await update.message.reply_text( 36 | "Это бот для автоматической проверки статуса очереди в Консульство РФ \n" 37 | "Вы можете прислать данные для проверки в одном из двух форматах: \n\n" 38 | "1) ссылка для записи на прием и проверки Вашей заявки - в письме от queue-robot@kdmid.ru 'Запись в список ожидания' \n" 39 | "2) через запятую ЛАТИНСКИМИ буквами: город, номер заявки, защитный код" 40 | ) 41 | return INFO 42 | 43 | 44 | async def info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> str: 45 | """Stores the info from the user.""" 46 | user_info = update.message.text 47 | print(user_info) 48 | 49 | kdmid_subdomain, order_id, code = user_info.strip().split(',') 50 | 51 | success_file = order_id+"_"+code+"_success.json" 52 | error_file = order_id+"_"+code+"_error.json" 53 | 54 | checker = QueueChecker() 55 | 56 | while True: 57 | 58 | message, status = checker.check_queue(kdmid_subdomain, order_id, code) 59 | await update.message.reply_text(f"Queue checking status: {message}") 60 | 61 | if os.path.isfile(success_file) or os.path.isfile(error_file): 62 | break 63 | 64 | time.sleep(1*30) # Pause for every_hours * hour before the next check 65 | 66 | await update.message.reply_text(f"Result: {message}") 67 | 68 | return ConversationHandler.END 69 | 70 | 71 | async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: 72 | """Cancels and ends the conversation.""" 73 | user = update.message.from_user 74 | logger.info("User %s canceled the conversation.", user.first_name) 75 | await update.message.reply_text( 76 | "Bye! I hope we can talk again some day.", reply_markup=ReplyKeyboardRemove() 77 | ) 78 | 79 | return ConversationHandler.END 80 | 81 | 82 | def main() -> None: 83 | """Run the bot.""" 84 | # Create the Application and pass it your bot's token. 85 | application = Application.builder().token(TOKEN).build() 86 | 87 | # Add conversation handler with the states INFO 88 | conv_handler = ConversationHandler( 89 | entry_points=[CommandHandler("start", start)], 90 | states={ 91 | INFO: [MessageHandler(filters.TEXT & ~filters.COMMAND, info)] 92 | }, 93 | fallbacks=[CommandHandler("cancel", cancel)], 94 | ) 95 | 96 | application.add_handler(conv_handler) 97 | 98 | # Run the bot until the user presses Ctrl-C 99 | application.run_polling(allowed_updates=Update.ALL_TYPES) 100 | 101 | 102 | if __name__ == "__main__": 103 | main() -------------------------------------------------------------------------------- /core/queue_checker.py: -------------------------------------------------------------------------------- 1 | 2 | import cv2 3 | import numpy as np 4 | import pytesseract 5 | import config 6 | import time 7 | import datetime 8 | import os 9 | import json 10 | 11 | import selenium 12 | from selenium import webdriver 13 | from selenium.common.exceptions import NoSuchElementException 14 | from selenium.webdriver.support.ui import WebDriverWait 15 | from selenium.webdriver.common.by import By 16 | from selenium.webdriver.support import expected_conditions as EC 17 | from selenium.webdriver.chrome.options import Options 18 | 19 | from webdriver_manager.chrome import ChromeDriverManager 20 | 21 | import base64 22 | from io import BytesIO 23 | from PIL import Image 24 | 25 | from core.image_processing import removeIsland 26 | 27 | import logging 28 | logging.basicConfig(filename='queue.log', 29 | filemode='a', 30 | format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', 31 | datefmt='%H:%M:%S', 32 | level=logging.INFO) 33 | 34 | pytesseract.pytesseract.tesseract_cmd = config.TESSERACT_PATH 35 | 36 | 37 | 38 | class QueueChecker: 39 | def __init__(self): 40 | self.kdmid_subdomain = '' 41 | self.order_id = '' 42 | self.code = '' 43 | self.url = 'http://'+self.kdmid_subdomain+'.kdmid.ru/queue/OrderInfo.aspx?id='+self.order_id+'&cd='+self.code 44 | self.image_name = 'captcha_processed.png' 45 | self.screen_name = "screenshot0.png" 46 | self.button_dalee = "//input[@id='ctl00_MainContent_ButtonA']" 47 | self.button_inscribe = "//input[@id='ctl00_MainContent_ButtonB']" 48 | self.main_button_id = "//input[@id='ctl00_MainContent_Button1']" 49 | self.text_form = "//input[@id='ctl00_MainContent_txtCode']" 50 | self.checkbox = "//input[@id='ctl00_MainContent_RadioButtonList1_0']" 51 | self.error_code = "//span[@id='ctl00_MainContent_Label_Message']" 52 | # self.error_code = "//div[@class='error_msg']" 53 | self.captcha_error = "//span[@id='ctl00_MainContent_lblCodeErr']" 54 | 55 | def get_url(self, kdmid_subdomain, order_id, code): 56 | url = 'http://'+kdmid_subdomain+'.kdmid.ru/queue/OrderInfo.aspx?id='+order_id+'&cd='+code 57 | self.kdmid_subdomain = kdmid_subdomain 58 | self.order_id = order_id 59 | self.code = code 60 | return url 61 | 62 | def write_success_file(self, text, status): 63 | d ={} 64 | d['status'] = status 65 | d['message'] = text 66 | if d['status'] == 'success': 67 | with open(self.order_id+"_"+self.code+"_success.json", 'w', encoding="utf-8") as f: 68 | json.dump(d, f) 69 | elif d['status'] == 'error': 70 | with open(self.order_id+"_"+self.code+"_error.json", 'w', encoding="utf-8") as f: 71 | json.dump(d, f) 72 | 73 | def check_exists_by_xpath(self, xpath, driver): 74 | mark = False 75 | try: 76 | driver.find_element(By.XPATH, xpath) 77 | mark = True 78 | return mark 79 | except NoSuchElementException: 80 | return mark 81 | 82 | def screenshot_captcha(self, driver, error_screen=None): 83 | # make a screenshot of the window, crop the image to get captcha only, 84 | # process the image: remove grey background, make letters black 85 | driver.save_screenshot("screenshot.png") 86 | 87 | screenshot = driver.get_screenshot_as_base64() 88 | img = Image.open(BytesIO(base64.b64decode(screenshot))) 89 | 90 | element = driver.find_element(By.XPATH, '//img[@id="ctl00_MainContent_imgSecNum"]') 91 | loc = element.location 92 | size = element.size 93 | 94 | left = loc['x'] 95 | top = loc['y'] 96 | right = (loc['x'] + size['width']) 97 | bottom = (loc['y'] + size['height']) 98 | screenshot = driver.get_screenshot_as_base64() 99 | #Get size of the part of the screen visible in the screenshot 100 | screensize = (driver.execute_script("return document.body.clientWidth"), 101 | driver.execute_script("return window.innerHeight")) 102 | img = img.resize(screensize) 103 | 104 | box = (int(left), int(top), int(right), int(bottom)) 105 | area = img.crop(box) 106 | area.save(self.screen_name, 'PNG') 107 | 108 | img = cv2.imread(self.screen_name) 109 | # Convert to grayscale 110 | c_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 111 | # Median filter 112 | out = cv2.medianBlur(c_gray,1) 113 | # Image thresholding 114 | a = np.where(out>228, 1, out) 115 | out = np.where(a!=1, 0, a) 116 | # Islands removing with threshold = 30 117 | out = removeIsland(out, 30) 118 | # Median filter 119 | out = cv2.medianBlur(out,3) 120 | cv2.imwrite(self.image_name, out*255) 121 | os.remove(self.screen_name) 122 | os.remove("screenshot.png") 123 | 124 | def recognize_image(self): 125 | digits = pytesseract.image_to_string(self.image_name, config='--psm 10 --oem 3 -c tessedit_char_whitelist=0123456789') 126 | return digits 127 | 128 | def check_queue(self, kdmid_subdomain, order_id, code): 129 | message = '' 130 | status = '' 131 | print('Checking queue for: {} - {}'.format(order_id, code)) 132 | logging.info('Checking queue for: {} - {}'.format(order_id, code)) 133 | chrome_options = Options() 134 | # chrome_options.add_argument("--headless") 135 | driver = webdriver.Chrome(ChromeDriverManager(driver_version='119.0.6045.124').install(), options=chrome_options) 136 | driver.maximize_window() 137 | url = self.get_url(kdmid_subdomain, order_id, code) 138 | driver.get(url) 139 | 140 | error = True 141 | error_screen = False 142 | # iterate until captcha is recognized 143 | while error: 144 | self.screenshot_captcha(driver, error_screen) 145 | digits = self.recognize_image() 146 | 147 | WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, self.text_form))).send_keys(str(digits)) 148 | 149 | time.sleep(1) 150 | # if the security code is wrong, expired or not from this order, stop the process 151 | try: 152 | element = WebDriverWait(driver, 10).until( 153 | EC.presence_of_element_located((By.XPATH, self.error_code)) 154 | ) 155 | status = 'error' 156 | message = 'The security code {} is written wrong, has expired or is not from this order. Theck it and try again.'.format(self.code) 157 | 158 | self.write_success_file(str(message), str(status)) 159 | logging.warning(f'{message}') 160 | break 161 | except: 162 | pass 163 | 164 | if self.check_exists_by_xpath(self.button_dalee, driver): 165 | driver.find_element(By.XPATH, self.button_dalee).click() 166 | 167 | if self.check_exists_by_xpath(self.button_inscribe, driver): 168 | driver.find_element(By.XPATH, self.button_inscribe).click() 169 | 170 | window_after = driver.window_handles[0] 171 | driver.switch_to.window(window_after) 172 | 173 | error = False 174 | 175 | try: 176 | driver.find_element(By.XPATH, self.main_button_id) 177 | except: 178 | error = True 179 | error_screen = True 180 | 181 | try: 182 | element = WebDriverWait(driver, 10).until( 183 | EC.presence_of_element_located((By.XPATH, self.text_form)) 184 | ) 185 | except: 186 | print("Element not found") 187 | 188 | driver.find_element(By.XPATH, self.text_form).clear() 189 | 190 | try: 191 | if self.check_exists_by_xpath(self.checkbox, driver): 192 | driver.find_element(By.XPATH,self.checkbox).click() 193 | check_box = driver.find_element(By.XPATH, self.checkbox) 194 | val = check_box.get_attribute("value") 195 | message = 'Appointment date: {}, time: {}, purpose: {}'.format( 196 | val.split('|')[1].split('T')[0], 197 | val.split('|')[1].split('T')[1], 198 | val.split('|')[-1] 199 | ) 200 | logging.info(message) 201 | WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, self.main_button_id))).click() 202 | status = 'success' 203 | self.write_success_file(message, str(status)) 204 | 205 | else: 206 | message = '{} - no free timeslots for now'.format(datetime.date.today()) 207 | status = 'in process' 208 | print(message) 209 | logging.info(message) 210 | except: 211 | message = '{} --- no free timeslots for now'.format(datetime.date.today()) 212 | logging.info(message) 213 | 214 | driver.quit() 215 | if os.path.exists(self.screen_name): 216 | os.remove(self.screen_name) 217 | if os.path.exists(self.image_name): 218 | os.remove(self.image_name) 219 | 220 | return message, status 221 | 222 | 223 | # checker = QueueChecker() 224 | 225 | # kdmid_subdomain = 'madrid' 226 | # order_id = '130238' 227 | # code = 'CD9E05C1' 228 | 229 | # 'madrid', '130238', 'CD9E05C1' 230 | # 'madrid', '151321', '5CCF3A7C' 231 | # checker.check_queue('madrid', '151321', '5CCF3A7C') 232 | 233 | # print(res, sta) --------------------------------------------------------------------------------