Task #79
๋ฌธ์ : L193์ 16์นธ ๋ค์ฌ์ฐ๊ธฐ (ํด๋์ค ๋ฉ์๋ ๋ด docstring์ 8์นธ์ด์ด์ผ ํจ)
์์ ๋ช ๋ น์ด
[๐ด HIGH] ## ์๋ฌด: razvedchik.py verification ์ฒ๋ฆฌ ๊ตฌํ (์ฌ์๋)
### ๋ฐฐ๊ฒฝ
ํ์คํฌ #77์ด ์๋ฃ๋๋ค๊ณ ๋ณด๊ณ ํ์ผ๋ ์ค์ ๋ก ์ฝ๋์ ๋ฐ์๋์ง ์์๋ค. ์ง์ ํ์ผ์ ์์ ํ๋ผ.
### ์ค์ verification ์๋ต ๊ตฌ์กฐ (๋ณด๊ณ ์์์ ํ์ธ๋จ)
๋๊ธ/ํฌ์คํธ ๊ฒ์ ํ ์๋ต์ด ์ด๋ฐ ๊ตฌ์กฐ๋ก ์จ๋ค:
```json
{
"comment": {
"id": "...",
"verification_status": "pending",
"verification": {
"verification_code": "moltbook_verify_916c8f1ac1881ffcc7959394eb36e6b6",
"challenge_text": "A] lOoObSsTtEr ClA-w^ eXErTs- TwEnTy] sIx Ne^ wToNs, aNd [AnOtHeR ClAw] aDdS- FoUrTeEn, WhAt/] iS- ToTaL FoRcE?",
"expires_at": "2026-03-25 20:10:31.072445+00",
"instructions": "Solve the math problem and respond with ONLY the number (with 2 decimal places, e.g., '525.00'). Send your answer to POST /api/v1/verify with the verification_code."
}
}
}
```
### challenge_text ํจํด ๋ถ์
obfuscate๋ ์์ด ํ
์คํธ. ์ซ์ ๋จ์ด(์๋จ์ด)๊ฐ ํฌํจ๋จ:
- "TwEnTy sIx" = 26, "FoUrTeEn" = 14 โ 26 + 14 = 40.00
- "ThIrTy NooToNs" = 30, "FiFtEeN" = 15 โ 30 + 15 = 45.00
- "ThIrTy ThReE nEuToNs", "SeVeN" โ 33 * 7 = 231.00 (dominance multiplies)
- "ThIrTy TwO NeWtOnS PlUs SiXtEeN" โ 32 + 16 = 48.00
ํต์ฌ: ์์ด ์ซ์ ๋จ์ด๋ฅผ ์ ์๋ก ๋ณํํ๊ณ , ์ฐ์ฐ์("plus", "and ... adds", "multiplies by")๋ฅผ ํ์
ํด์ ๊ณ์ฐ.
### verify API
```
POST /api/v1/verify
Authorization: Bearer {api_key}
Content-Type: application/json
{"verification_code": "moltbook_verify_...", "answer": "40.00"}
```
### ๊ตฌํํ ๋ด์ฉ
ํ์ผ: `/home/grass/leninbot/razvedchik.py` (ํ์ฌ 1024์ค)
#### 1. `MoltbookClient`์ ๋ ๋ฉ์๋ ์ถ๊ฐ (L189 ์งํ, L190 ๋น ์ค ์๋ฆฌ)
```python
@staticmethod
def solve_challenge_text(challenge_text: str) -> str:
"""
obfuscate๋ ์์ด ์ฑ๋ฆฐ์ง ํ
์คํธ์์ ์ํ ๋ฌธ์ ์ถ์ถ ํ ๊ณ์ฐ.
๋ฐํ: "40.00" ํ์ ๋ฌธ์์ด
"""
# ์๋จ์ด โ ์ ์ ๋งคํ
word_to_num = {
'zero':0,'one':1,'two':2,'three':3,'four':4,'five':5,
'six':6,'seven':7,'eight':8,'nine':9,'ten':10,
'eleven':11,'twelve':12,'thirteen':13,'fourteen':14,'fifteen':15,
'sixteen':16,'seventeen':17,'eighteen':18,'nineteen':19,'twenty':20,
'thirty':30,'forty':40,'fifty':50,'sixty':60,'seventy':70,
'eighty':80,'ninety':90,'hundred':100,
# ์คํ/๋ณํ (์ค์ ์ฑ๋ฆฐ์ง์์ ๊ด์ฐฐ๋จ)
'nooton':1,'nootons':1,'neuton':1,'neutons':1,
}
# ํ
์คํธ ์ ๊ทํ: ํน์๋ฌธ์/์ผ์ด์ค ์ ๊ฑฐ
text = re.sub(r'[^a-zA-Z0-9\s]', ' ', challenge_text).lower()
words = text.split()
# ์ซ์ ๋จ์ด ์ํ์ค์์ ์ซ์ ์ถ์ถ
def extract_numbers(words):
nums = []
i = 0
while i < len(words):
w = words[i]
if w in word_to_num:
val = word_to_num[w]
# "twenty six" ๋ฑ ๋ณตํฉ ์ซ์
if val in (20,30,40,50,60,70,80,90) and i+1 < len(words) and words[i+1] in word_to_num and word_to_num[words[i+1]] < 10:
val += word_to_num[words[i+1]]
i += 1
# "hundred" ์ฒ๋ฆฌ
if i+1 < len(words) and words[i+1] == 'hundred':
val *= 100
i += 1
nums.append(val)
i += 1
return nums
numbers = extract_numbers(words)
logger.debug("[razvedchik] ์ฑ๋ฆฐ์ง ์ถ์ถ ์ซ์: %s from '%s'", numbers, challenge_text[:60])
if not numbers:
logger.warning("[razvedchik] ์ฑ๋ฆฐ์ง ์ซ์ ์ถ์ถ ์คํจ")
return "0.00"
# ์ฐ์ฐ์ ํ๋จ
# "multiplies by" โ ๊ณฑ์
if 'multipli' in text or 'multiply' in text or 'times' in text:
result = 1
for n in numbers:
result *= n
else:
# ๊ธฐ๋ณธ: ๋ง์
(plus, adds, and, total)
result = sum(numbers)
return f"{result:.2f}"
def _verify_content(self, verification: dict) -> bool:
"""
๋๊ธ/ํฌ์คํธ ๊ฒ์ ํ ์๋ต์ verification ํ๋๋ฅผ ์ฒ๋ฆฌํด verify API ํธ์ถ.
Args:
verification: response["comment"]["verification"] ๋๋ response["post"]["verification"]
Returns:
True if verified, False otherwise
"""
if not verification:
return False
code = verification.get("verification_code", "")
challenge_text = verification.get("challenge_text", "")
expires_at = verification.get("expires_at", "")
if not code or not challenge_text:
logger.warning("[razvedchik] verification ํ๋ ๋ถ์์ : %s", verification)
return False
answer = self.solve_challenge_text(challenge_text)
logger.info("[razvedchik] Verification ์๋ โ code=%s, answer=%s", code[:30], answer)
try:
resp = self._request("POST", "/verify", json={"verification_code": code, "answer": answer})
logger.info("[razvedchik] Verification ์๋ต: %s", resp)
return True
except Exception as e:
logger.warning("[razvedchik] Verification ์คํจ: %s", e)
return False
```
#### 2. `post_comment()` ์์ (ํ์ฌ L429~454)
๊ฒ์ ํ `resp` ์์ verification ์ถ์ถํ๊ณ `_verify_content()` ํธ์ถ:
```python
def post_comment(self, post_id, content, parent_id=None):
# ... ๊ธฐ์กด ์ฝ๋ ...
resp = self.client.post(f"/posts/{post_id}/comments", body)
# verification ์ฒ๋ฆฌ ์ถ๊ฐ
comment_data = resp.get("comment", {})
verification = comment_data.get("verification")
if verification:
verified = self.client._verify_content(verification)
logger.info("[razvedchik] ๋๊ธ verification: %s", "โ
" if verified else "โ")
return resp
```
#### 3. `post_observation()` ์์ (ํ์ฌ L457~483)
๋์ผํ๊ฒ ๊ฒ์ ํ `resp["post"]["verification"]` ์ฒ๋ฆฌ:
```python
def post_observation(self, topic, content, submolt="general"):
# ... ๊ธฐ์กด ์ฝ๋ ...
resp = self.client.post("/posts", body)
# verification ์ฒ๋ฆฌ ์ถ๊ฐ
post_data = resp.get("post", {})
verification = post_data.get("verification")
if verification:
verified = self.client._verify_content(verification)
logger.info("[razvedchik] ํฌ์คํธ verification: %s", "โ
" if verified else "โ")
return resp
```
### ์ ์ฐจ
1. `/home/grass/leninbot/razvedchik.py` ๋ฐฑ์
(`razvedchik.py.bak.YYYYMMDD_HHMMSS`)
2. ์ 3๊ฐ์ง ์์ ์ ์ฉ
3. `python -c "import ast; ast.parse(open('/home/grass/leninbot/razvedchik.py').read()); print('OK')"` ๊ตฌ๋ฌธ ๊ฒ์ฆ
4. `cd /home/grass/leninbot && git add razvedchik.py && git commit -m "feat: verification ์ฒ๋ฆฌ ๊ตฌํ (post_comment/post_observation ํ ์๋ verify)" && git push`
5. ์๋ฃ ๋ณด๊ณ
### ์ฃผ์
- ๊ธฐ์กด ์ฝ๋ ๊ตฌ์กฐ ์ต๋ํ ์ ์ง
- `solve_verification()` (๊ธฐ์กด ๋ฉ์๋, L160~189) ๋ ์ญ์ ํ์ง ๋ง ๊ฒ โ `_request()` ์์ ์ฌ์ ํ ์ฌ์ฉ
- `_verify_content()`๋ `MoltbookClient`์ ์ธ์คํด์ค ๋ฉ์๋ (self ํ์)
- `solve_challenge_text()`๋ staticmethod