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