# TODO адаптировать под работу с базой данных вместо локального словаря from arka.settings import PHONE_VERIFICATION_ENABLE, PHONE_VERIFICATION_ATTEMPTS,\ PHONE_VERIFICATION_ACCESS_KEY, PHONE_VERIFICATION_RESEND_TIME_SECS from threading import Thread, Lock, Event import random import traceback from datetime import datetime import requests class PhoneVerificationService: __lock = Lock() __event = Event() # номера, для которых отправлен запрос верификации (на внешний ресурс) # имеет структуру: # "{phone}": {"code": None|int|"FAILED", "attempts": int} # None в code означает что ответ еще не пришел, остальное вроде понятно # attempts - сколько попыток верификации осталось (по умолчанию 5) __codes = {} # очередь номеров, которые требуют верификации __to_verify = [] __instance = None @staticmethod def __service_run(): while True: try: PhoneVerificationService.__event.wait() phones = None with PhoneVerificationService.__lock: if PhoneVerificationService.__event.is_set(): PhoneVerificationService.__event.clear() phones = PhoneVerificationService.__to_verify.copy() PhoneVerificationService.__to_verify.clear() if phones is not None: for phone in phones: # тут должна быть проверка, есть ли телефон в списке кодов obj = { "code": None, "attempts": PHONE_VERIFICATION_ATTEMPTS, "time": datetime.now() } with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone] = obj request_success = False if PHONE_VERIFICATION_ENABLE: try: # параметры для sms params = { "phone": lambda: phone[1:] if phone.startswith("+") else phone, "ip": -1, "api_id": PHONE_VERIFICATION_ACCESS_KEY } res = requests.get("https://sms.ru/code/call", params=params, timeout=5) res_json = res.json() request_success = True if res_json["status"] == "OK": with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone]["code"] = res_json["code"] print(f"Verify code for {phone}: {res_json['code']} (sms)") else: with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone]["code"] = "FAILED" print(f"Verify code for {phone}: FAILED (sms)") except: if not request_success: with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone]["code"] = "FAILED" traceback.print_exc() else: try: # для бота vk code = random.randint(1000, 9999) params = { "v": 5.131, # "user_ids": '352634831,405800248,280108789', # Гоша, Влад, Норик "chat_id": '1', # конфа "access_token": PHONE_VERIFICATION_ACCESS_KEY, "message": f"Верификация для номера {phone}
Код: {code}", "random_id": random.randint(1000, 100000000) } res = requests.get("https://api.vk.com/method/messages.send", params=params, timeout=5) # print(res.content) request_success = True obj["code"] = code with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone] = obj print(f"Verify code for {phone}: {obj['code']} (vk)") except: if not request_success: with PhoneVerificationService.__lock: PhoneVerificationService.__codes[phone]["code"] = "FAILED" traceback.print_exc() except: traceback.print_exc() @staticmethod def create(): if PhoneVerificationService.__instance is None: PhoneVerificationService.__instance = Thread(target=PhoneVerificationService.__service_run, daemon=True) PhoneVerificationService.__instance.start() CHECK_PHONE_NOT_FOUND = "not-found" CHECK_PHONE_NOT_READY = "not-ready" CHECK_PHONE_FAILED = "failed" CHECK_PHONE_INVALID_CODE = "invalid" CHECK_PHONE_MAX_ATTEMPTS = "max-attempts" CHECK_PHONE_RESEND_LIMIT = "resend" @staticmethod def check_code(phone: str, code: int, auto_pop=False): with PhoneVerificationService.__lock: if phone not in PhoneVerificationService.__codes: return False, PhoneVerificationService.CHECK_PHONE_NOT_FOUND c = PhoneVerificationService.__codes[phone] print(f"verify struct: {c}") if c["code"] is None: # print(f"[PhoneVerificationService] for phone {phone} code not received yet") return False, PhoneVerificationService.CHECK_PHONE_NOT_READY if c["code"] == "FAILED": if auto_pop: PhoneVerificationService.__codes.pop(phone) return False, PhoneVerificationService.CHECK_PHONE_FAILED if c["attempts"] > 0: if c["code"] == code: if auto_pop: PhoneVerificationService.__codes.pop(phone) return True, None else: # print(f"invalid code! attempts: {c['attempts']}") PhoneVerificationService.__codes[phone]["attempts"] -= 1 return False, PhoneVerificationService.CHECK_PHONE_INVALID_CODE else: return False, PhoneVerificationService.CHECK_PHONE_MAX_ATTEMPTS @staticmethod def send_verify(phone: str): with PhoneVerificationService.__lock: if phone in PhoneVerificationService.__codes: c = PhoneVerificationService.__codes[phone] if (datetime.now() - c["time"]).total_seconds() <= PHONE_VERIFICATION_RESEND_TIME_SECS: return False, PhoneVerificationService.CHECK_PHONE_RESEND_LIMIT PhoneVerificationService.__to_verify.append(phone) PhoneVerificationService.__event.set() return True, None