[에러 해결] Python NameError: name ‘generate_chk’ is not defined 원인과 해결 방법
Python 개발 중 NameError: name '...' is not defined 에러는 흔히 마주치는 문제입니다. 특히 외부 API 문서나 오래된 코드 스니펫을 활용할 때 이러한 에러가 발생하기 쉽습니다. 이번 가이드에서는 Geometry Dash 레벨 업로드 프로그램을 파이썬으로 개발하던 중 발생한 NameError: name 'generate_chk' is not defined 에러의 원인을 분석하고, 실제 Geometry Dash API의 동작 방식에 기반한 해결책을 제시합니다.
에러 상황 소개
Geometry Dash 레벨 업로드 자동화 프로그램을 Python으로 작성하던 중, uploadGJLevel21 엔드포인트에 필요한 seed2 값을 생성하는 과정에서 다음과 같은 NameError가 발생했습니다. 문제는 generate_chk라는 함수를 호출하고 있었지만, 해당 함수가 프로그램 내 어디에도 정의되어 있지 않다는 것이었습니다.
Traceback (most recent call last):
File "/home/***/scripts/usernm/upload", line 43, in <module>
"seed2": generate_chk(key="41274", values=[generate_upload_seed(levelString)], salt="xI25fpAapCQg"), # This is talked about in the CHK encryption,
^^^^^^^^^^^^
NameError: name 'generate_chk' is not defined. Did you mean: 'generate_gjp2'?
이 에러는 data 딕셔너리의 "seed2" 키 값을 할당하는 라인에서 발생했습니다. 개발자는 requests, base64, hashlib 모듈에 이 함수가 있을 것이라고 추측했지만, 실제로는 존재하지 않았습니다.
발생 원인
NameError: name 'generate_chk' is not defined 에러의 핵심 원인은 이름 그대로 generate_chk 함수가 파이썬 인터프리터가 접근할 수 있는 어떤 스코프에도 정의되어 있지 않기 때문입니다. 파이썬은 변수나 함수를 사용하기 전에 반드시 선언(정의)되어 있어야 합니다.
- 함수 미정의: 사용자가 코드 상에서
generate_chk함수를 직접 정의하지 않았습니다. - 외부 라이브러리 오해:
requests,base64,hashlib와 같은 표준 라이브러리나 흔히 사용되는 외부 라이브러리에는generate_chk라는 이름의 함수가 기본적으로 포함되어 있지 않습니다. 이 함수는 Geometry Dash API의 특정 체크섬(CHK) 생성을 위한 커스텀 로직이며, 개발자가 직접 구현하거나 관련 API 래퍼 라이브러리에서 가져와야 합니다. - 문서의 불완전성 또는 오해: 참조했던
https://wyliemaster.github.io/gddocs문서는 아카이브되어 더 이상 업데이트되지 않으며, 해당 문서가 특정 헬퍼 함수(generate_chk,generate_upload_seed등)가 이미 정의되어 있음을 가정하고 코드를 제시했을 가능성이 높습니다.
generate_chk 함수는 key, values, salt를 인자로 받아 seed2 값을 생성하는 역할을 합니다. Geometry Dash API의 seed2는 일반적으로 levelString의 아스키 값 합을 특정 키(예: 41274)와 XOR 연산한 후, 이를 문자열로 변환하고 salt를 붙여 SHA-1 해시하는 방식으로 구현됩니다.
해결 방법 코드 예시
이 에러를 해결하기 위해서는 Geometry Dash API의 seed2 생성 로직에 맞춰 generate_upload_seed와 generate_chk 함수를 직접 구현해야 합니다.
에러 코드 (original snippet)
# ... (생략) ...
levelString = "H4sIAAAAAAAAC6WQwQ3DIAxFF3IlfxsIUU6ZIQP8AbJChy_GPSZqpF7-A4yfDOfhXcCiNMIqnVYrgYQl8rDwBTZCVbkQRI3oVHbiDU6F2jMF_lesl4q4kw2PJMbovxLBQxTpM3-I6q0oHmXjzx7N0240cu5w0UBNtESRkblevSLHjh8nTubmYJZ2MvMrEITEN0gEJMxlLiMZ28frmj"
data = {
# ... (생략) ...
"levelString": levelString,
"seed2": generate_chk(key="41274", values=[generate_upload_seed(levelString)], salt="xI25fpAapCQg"), # <-- 여기서 에러 발생
"secret": "Wmfd2893gb7"
}
# ... (생략) ...
수정된 코드
generate_upload_seed와 generate_chk 함수를 정의하여 seed2 값을 올바르게 생성하도록 수정합니다. generate_upload_seed는 levelString의 아스키 코드 합을 반환하고, generate_chk는 이 합과 제공된 key를 XOR 연산한 후 salt를 추가하여 SHA-1 해시를 계산합니다.
import requests
import base64
import hashlib # sha1() lives there
user = input("What is your username?")
accid = input("What is your Account ID?")
passwd = input("What is your password?")
lname = input ("What is the Level name?")
coin = input("How many coins are there?")
stars = input("How many stars do you want?")
def generate_gjp2(password: str = passwd, salt: str = "mI29fmAnxgTs") -> str:
password += salt
hash = hashlib.sha1(password.encode()).hexdigest()
return hash
# --- 새로 추가된 함수 정의 시작 ---
def generate_upload_seed(level_string: str) -> int:
# Geometry Dash API의 'seed' 생성 방식에 따라 levelString의 아스키 코드 합을 반환합니다.
# 이 값은 generate_chk 함수에서 추가적인 연산에 사용됩니다.
return sum(ord(c) for c in level_string)
def generate_chk(key: str, values: list, salt: str) -> str:
# 'values'는 generate_upload_seed(levelString)의 결과(levelString의 아스키 합)를 포함합니다.
# 'key'는 XOR 연산에 사용되며, 'salt'는 최종 해시 문자열에 추가됩니다.
# values 리스트의 첫 번째 요소(levelString의 아스키 합)를 가져옵니다.
sum_of_level_chars = values[0]
# key를 정수형으로 변환합니다.
key_int = int(key)
# sum_of_level_chars와 key_int를 XOR 연산합니다.
xor_result = sum_of_level_chars ^ key_int
# XOR 결과와 salt를 연결하여 해시할 문자열을 만듭니다.
string_to_hash = str(xor_result) + salt
# SHA-1 해시를 계산하여 반환합니다.
return hashlib.sha1(string_to_hash.encode()).hexdigest()
# --- 새로 추가된 함수 정의 끝 ---
levelString = "H4sIAAAAAAAAC6WQwQ3DIAxFF3IlfxsIUU6ZIQP8AbJChy_GPSZqpF7-A4yfDOfhXcCiNMIqnVYrgYQl8rDwBTZCVbkQRI3oVHbiDU6F2jMF_lesl4q4kw2PJMbovxLBQxTpM3-I6q0oHmXjzx7N0240cu5w0UBNtESRkble7SLHjh8nTubmYJZ2MvMrEITEN0gEJMxlLiMZ28frmj"
data = {
"gameVersion": 22,
"accountID"": accid,
"gjp": generate_gjp2(), # generate_gjp2 호출, passwd와 salt를 인자로 넘기도록 수정하거나 전역변수 사용
"userName": user,
"levelID": 0,
"levelName": lname,
"levelDesc": "Q3Vyc2VkIGxldmVsIG55ZWggaGVoIGhlaCBhbnl3YXkgaXRzIGEgdmVyeSBicm9rZW4gbGV2ZWwgaSBzZW50IGEgcmVxdWVzdCB0byByb2Igc2VydmVyIHRocm91Z2ggcHl0aG9uIGxvbA",
"levelVersion": "-47577473775738573",
"levelLength": 48483845738573486574873869465947694,
"audioTrack": 0,
"auto": 0,
"password": 314159,
"original": 55610687,
"twoPlayer": 0,
"songID": 839583504693858468444987694,
"objects": 1,
"coins": coin,
"requestedStars": stars,
"unlisted": 0,
"ldm": 0,
"levelString": levelString,
"seed2": generate_chk(key="41274", values=[generate_upload_seed(levelString)], salt="xI25fpAapCQg"), # 이제 이 함수들이 정의되어 에러가 발생하지 않습니다.
"secret": "Wmfd2893gb7"
}
headers = {
"User-Agent": ""
}
url = "http://www.boomlings.com/database/uploadGJLevel21.php"
req = requests.post(url=url, data=data, headers=headers)
print(req.text)
참고: 원래 코드의 "gjp": hash, 부분은 generate_gjp2() 함수를 호출하여 값을 할당해야 합니다. hash라는 변수가 직접 정의되지 않았고, generate_gjp2 함수가 선언되었음에도 호출되지 않았기 때문입니다. 위 수정된 코드에서는 "gjp": generate_gjp2(), 로 변경했습니다. 또한 generate_gjp2 함수가 전역 변수 passwd를 사용하도록 정의되어 있지만, 함수 내부에서 직접 인자로 받는 것이 더 좋은 코드 스타일입니다.
향후 예방을 위한 팁
- 함수 및 변수 정의 확인: 코드를 작성하거나 외부 코드를 사용할 때, 모든 함수와 변수가 사용 전에 정의되어 있는지 항상 확인하는 습관을 들여야 합니다. IDE의 경고 메시지를 주의 깊게 살펴보는 것이 큰 도움이 됩니다.
- API 문서 최신화 및 교차 검증: 아카이브되었거나 오래된 API 문서를 참조할 때는 해당 API의 최신 문서나 다른 활성 커뮤니티(예: GitHub의 관련 프로젝트)를 통해 정보를 교차 검증하는 것이 중요합니다. API는 시간이 지남에 따라 변경될 수 있으며, 누락된 헬퍼 함수가 있을 수 있습니다.
- 핵심 로직 이해: 단순히 함수 이름을 복사 붙여넣기하는 것을 넘어, 체크섬, 해시, 암호화 등 핵심 로직이 어떻게 작동하는지 이해하려고 노력해야 합니다. 이는 디버깅을 용이하게 하고, 에러 발생 시 스스로 해결할 수 있는 능력을 길러줍니다.
- 모듈화 및 재사용 가능한 코드 작성: 특정 API와의 상호작용을 위한 헬퍼 함수들은 별도의 모듈로 분리하여 관리하면 코드의 가독성을 높이고 다른 프로젝트에서도 재사용하기 좋습니다.
- 예외 처리 및 로깅: API 요청 시 발생할 수 있는 다양한 예외 상황(네트워크 에러, 응답 에러 등)에 대비하여 적절한 예외 처리(try-except)와 로깅을 구현하면 문제 발생 시 원인 파악에 큰 도움이 됩니다.
답글 남기기