Last active
September 17, 2021 07:53
-
-
Save qkdxorjs1002/89a866803cc758543dbedfc52cdb6d76 to your computer and use it in GitHub Desktop.
Researching: How to implement processes of auth API and vaccine reservation API for Kakao 'no-show' Vaccine reservation system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import copy | |
import sys | |
from time import sleep | |
import js2py | |
import requests | |
import urllib3 | |
from bs4 import BeautifulSoup | |
urllib3.disable_warnings() | |
USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 KAKAOTALK 9.4.6" | |
with requests.Session() as session: | |
def getCookies(userEmail, userPw): | |
try: | |
print("\n== Request Token and Encryption Key ==") | |
loginPage = session.get( | |
"https://accounts.kakao.com/login?continue=https%3A%2F%2Faccounts.kakao.com%2Fweblogin%2Faccount%2Finfo", | |
headers = { | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", | |
"User-Agent": USER_AGENT, | |
"Referer": "https://accounts.kakao.com/" | |
} | |
) | |
print(f" - LoginPage Status Code: {loginPage.status_code}") | |
loginSoup = BeautifulSoup(loginPage.text, "html.parser") | |
token = loginSoup.select("head > meta:nth-child(3)")[0]['content'] | |
key = loginSoup.select("#login-form > fieldset > input[type=hidden]:nth-child(3)")[0]['value'] | |
print(f" - Token: {token}") | |
print(f" - Encryption Key: {key}") | |
print("\n== Request Session Params ==") | |
tiaraTrack = session.get( | |
"https://stat.tiara.kakao.com/track?d=%7B%22sdk%22%3A%7B%22type%22%3A%22WEB%22%2C%22version%22%3A%221.1.15%22%7D%2C%22env%22%3A%7B%22screen%22%3A%221441X2561%22%2C%22tz%22%3A%22%2B9%22%2C%22cke%22%3A%22Y%22%7D%2C%22common%22%3A%7B%22svcdomain%22%3A%22accounts.kakao.com%22%2C%22deployment%22%3A%22production%22%2C%22url%22%3A%22https%3A%2F%2Faccounts.kakao.com%2Flogin%22%2C%22referrer%22%3A%22https%3A%2F%2Faccounts.kakao.com%2F%22%2C%22title%22%3A%22%EC%B9%B4%EC%B9%B4%EC%98%A4%EA%B3%84%EC%A0%95%22%2C%22section%22%3A%22login%22%2C%22page%22%3A%22pageLogin%22%7D%2C%22action%22%3A%7B%22type%22%3A%22Pageview%22%2C%22name%22%3A%22pageLogin%22%2C%22kind%22%3A%22%22%7D%7D", | |
headers = { | |
"User-Agent": USER_AGENT, | |
"Referer": "https://accounts.kakao.com/" | |
} | |
) | |
print(f" - Tiara Status Code: {tiaraTrack.status_code}") | |
print("\n== Encrypt User Email & PW ==") | |
CryptoJS = js2py.require('crypto-js') | |
encryptEmail = CryptoJS.AES.encrypt(userEmail, key).toString() | |
encryptPw = CryptoJS.AES.encrypt(userPw, key).toString() | |
print(f" - Cipher Email: {encryptEmail}") | |
print(f" - Cipher PW: {encryptPw}") | |
print("\n== Request Auth ==") | |
authenticate = session.post( | |
"https://accounts.kakao.com/weblogin/authenticate.json", | |
headers = { | |
"Accept-Encoding": "gzip, deflate, br", | |
"Accept-Language": "ko,en;q=0.9,en-US;q=0.8", | |
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8", | |
"Host": "accounts.kakao.com", | |
"User-Agent": USER_AGENT, | |
"Origin": "https://accounts.kakao.com", | |
"Referer": "https://accounts.kakao.com/login?continue=https%3A%2F%2Faccounts.kakao.com%2Fweblogin%2Faccount%2Finfo", | |
"X-Requested-With": "XMLHttpRequest" | |
}, | |
data = { | |
"os": "web", | |
"webview_v": 2, | |
"continue": "https://accounts.kakao.com/weblogin/account/info", | |
"email": encryptEmail, | |
"password": encryptPw, | |
"third": "false", | |
"k": "true", | |
"authenticity_token": token | |
} | |
) | |
print(f" - Auth Status Code: {authenticate.status_code}") | |
if (authenticate.json()["status"] != 0): | |
sys.exit(authenticate.text) | |
print("\n== Request Auth Token==") | |
ssoInit = session.get( | |
"https://accounts.kakao.com/weblogin/sso_initialize?continue=https%3A%2F%2Fmap.kakao.com", | |
headers = { | |
"User-Agent": USER_AGENT | |
} | |
) | |
print(f" - SSO Status Code: {ssoInit.status_code}") | |
print("\n== Request Token Login ==") | |
tokenLogin = session.get( | |
f"https://logins.daum.net/accounts/kakaossotokenlogin.do?redirect=false&ssotoken={ssoInit.json()['tokens'][0]['token']}", | |
headers = { | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", | |
"Accept-Encoding": "gzip, deflate, br", | |
"Accept-Language": "ko,en;q=0.9,en-US;q=0.8", | |
"Referer": "https://accounts.kakao.com/", | |
"User-Agent": USER_AGENT | |
} | |
) | |
print(f" - Token Login Status Code : {tokenLogin.status_code}") | |
if (tokenLogin.status_code == 200): | |
print(f"\n!!!! Login Success !!!!") | |
else: | |
sys.exit("\n!!!! Failed to Login !!!!") | |
except Exception as e: | |
sys.exit(e) | |
def testCookie(): | |
try: | |
userInfo = session.get( | |
"https://vaccine.kakao.com/api/v1/user", | |
headers = { | |
"User-Agent": USER_AGENT | |
}, | |
verify = False | |
) | |
userJson = userInfo.json() | |
if (userInfo.status_code == 200) : | |
print(f" - Name: {userJson['user']['name']}") | |
print(f" - Status: {userJson['user']['status']}") | |
print(f" - Reservations: {len(userJson['reservations'])}") | |
return True | |
except Exception as e: | |
print(e) | |
return False | |
def requestOrgs(coords, onlyLeft = True): | |
temp = copy.deepcopy(coords) | |
temp["onlyLeft"] = onlyLeft | |
return session.post( | |
"https://vaccine.kakao.com/api/v3/vaccine/left_count_by_coords", | |
json = temp, | |
verify = False | |
) | |
def requestResrv(org, type): | |
orgResp = session.get( | |
f"https://vaccine.kakao.com/api/v3/org/org_code/{org['orgCode']}", | |
headers = { | |
"Accept": "application/json, text/plain, */*", | |
"Content-Type": "application/json;charset=utf-8", | |
"Origin": "https://vaccine.kakao.com", | |
"Accept-Language": "en-us", | |
"User-Agent": USER_AGENT, | |
"Referer": f"https://vaccine.kakao.com/", | |
"Accept-Encoding": "gzip, deflate", | |
"Connection": "close" | |
}, | |
verify = False | |
) | |
orgResp_json = orgResp.json() | |
leftCnt = 0; | |
for x in orgResp_json['lefts']: | |
if (x['vaccineCode'] == type): | |
leftCnt += x['leftCount'] | |
if (leftCnt == 0): | |
return False | |
for x in range(leftCnt): | |
req_x = session.post( | |
"https://vaccine.kakao.com/api/v2/reservation", | |
json = { | |
"from": "Map", | |
"vaccineCode": type, | |
"orgCode": int(org["orgCode"]), | |
"distance": None | |
}, | |
headers = { | |
"Accept": "application/json, text/plain, */*", | |
"Content-Type": "application/json;charset=utf-8", | |
"Origin": "https://vaccine.kakao.com", | |
"Content-Length": "74", | |
"Accept-Language": "en-us", | |
"User-Agent": USER_AGENT, | |
"Referer": f"https://vaccine.kakao.com/reservation/{org['orgCode']}?from=Map&code={type}", | |
"Accept-Encoding": "gzip, deflate", | |
"Connection": "close" | |
}, | |
verify = False | |
) | |
print(req_x.text) | |
if (req_x.status_code == 200) : | |
return True | |
return False | |
def reserveLoop(coords, type, interval): | |
isReserved = False; | |
while True: | |
try: | |
resp = requestOrgs(coords) | |
resp_json = resp.json() | |
for a in resp_json["organizations"]: | |
if (requestResrv(a, type)) : | |
isReserved = True | |
if (isReserved) : | |
print("!!!! Reserved !!!!\n\n") | |
if (resp.status_code != 200): | |
print(resp.status_code) | |
sleep(interval) | |
except Exception as e: | |
print(e) | |
pass | |
def parsePosition(leftTop, rightBottom): | |
lt = leftTop.split(", ") | |
rb = rightBottom.split(", ") | |
return { | |
"bottomRight": { | |
"x": lt[1], | |
"y": lt[0] | |
}, | |
"onlyLeft": True, | |
"order": "count", | |
"topLeft": { | |
"x": rb[1], | |
"y": rb[0] | |
} | |
} | |
###################################################### | |
## 무조건 하단 절차 확인해야 오류 안남 | |
###################################################### | |
## 필수! pip install wheel js2py numpy requests bs4 | |
## | |
## 0. 카카오톡에서 지갑 인증서 발급 되어 있는지 확인하기 | |
## 0. 카카오톡에서 #탭 - 잔여백신에서 아무 기관에 알람 신청하기 (개인정보 수집 동의) | |
## 1. "python vaccine.py" 혹은 "python3 vaccine.py" 로 스크립트 실행 | |
## | |
## !. 오류 401 "{"error": "error occurred"}"가 뜰 경우 로그인 정보 올바른지, 알림 신청시 개인정보 수집 동의를 했는지 확인 | |
## !. 오류 401 "{"error": "User not found: accountId=<숫자>"}"가 뜰 경우 카카오톡에서 지갑 인증서 발급 및 인증되어 있는지 확인 | |
if (__name__ == "__main__"): | |
TYPE_ALL = "ANY" | |
TYPE_PFIZER = "VEN00013" | |
TYPE_MODERNA = "VEN00014" | |
TYPE_AZ = "VEN00015" | |
TYPE_JANSSEN = "VEN00016" | |
#################################################################### | |
#################################################################### | |
## 카카오 계정 이메일 | |
userEmail = "value" | |
## 카카오 계정 패스워드 | |
userPw = "value" | |
## 구글 지도 통해서 사각형 범위를 지정해주세요 | |
## 왼쪽 상단 좌표 | |
posLeftTop = "value" | |
## 오른쪽 하단 좌표 | |
posRightBottom = "value" | |
## 백신 종류입니다. | |
## 전부: TYPE_ALL / 화이자: TYPE_PFIZER / 모더나: TYPE_MODERNA | |
## 아스트라제네카: TYPE_AZ / 얀센: TYPE_JANSSEN | |
type = TYPE_PFIZER; | |
#################################################################### | |
#################################################################### | |
print("\n== Check Coords ==") | |
coords = parsePosition(posLeftTop, posRightBottom) | |
print(f" - Org count: {len(requestOrgs(coords, onlyLeft = False).json()['organizations'])}") | |
print("\n== Login ==") | |
getCookies(userEmail, userPw) | |
print("\n== Check Cookies ==") | |
if(testCookie()): | |
print("\n!!!! Valid Cookie !!!!") | |
else: | |
sys.exit("\n!!!! Invalid Cookie !!!!") | |
print("\n== Reservation Loop ==") | |
reserveLoop( | |
## 지역 범위 | |
coords = coords, | |
## 백신 종류 | |
type = type, | |
## 요청 간격 낮을 수록 예약 확률이 높아지지만, 서버에서 요청을 차단할 가능성 있음 (기본값: 0.35초) | |
interval = 0.35 | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment