From 012ee66b883ac77e86984dcbd4465480e7b7f76d Mon Sep 17 00:00:00 2001 From: banggae Date: Sun, 7 Jun 2026 12:03:52 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=90=EC=9C=A0=EA=B2=8C=EC=8B=9C=ED=8C=90?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/app.cpython-313.pyc | Bin 4916 -> 8402 bytes app.py | 97 ++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index b656a394f73bfc8e5d71062d2bd53d7fba834df4..7605a0c0db6885f079b02e797061b9daa4ef0ce6 100644 GIT binary patch literal 8402 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 delta 1652 zcmbu9PfR0K9LL|AX{YVZrF7m-LiJpra++~-4>@s zvkfsGjL~=j-(eR$u--TsA|CLdYfRSQdax^QAmb+J!9;JE#b~05&a|muM3aq^%``FS^>{+zLU{4jZ8u40rQG^h1ZOqfC-}P!YQK-zYi}$HU0xO z62B_9SmF$%kTId+%=og3rO!*b>1zPQ0bU_&4(n-UkvWcEAZ?gZFsuO#5JN;QDz8pD z>hT;aZ|bTAS>tL0K+FIcS0UR8zpEa3Jfeo6GOvmQ&VBr?u8@5Q*0e`r<67QQGi*XB zRc=gB5(8OhsCwJvtvIT(yQHV27uXoI63X@E;`Z|4*Xv#5M9kt97LLZylKfwV&l} znGm601Z5mIe$OjI*-Uy?RE!sjr81o>%%pQ!Ne-9wT~$x=`S3Mg*jsMk&xWSc#f&E_ zWadWr)@-S?IPCYADL>!p@rQqu&zzSKl{1BxhxuP*P|JMY^&H>geD_T-)9hsnK>$@?Rdl~BCWe`VL6SW!H5HmwKV z4(v1S`}W?A*^MikfeJIc&$#wk=R?-D-n-WOla_K>R@8?kpf}?$8yifUx{a5&pW9J= za--7w?AJODUt(F;)OuX`;}p3=>7~hzHR8KKkoO3Aa?cp?#0JSP z29>cOzT*tR5dPii><>$YA4x9|UNy>TA}K881x`}otw^&OQWKK&HsC4u1y_gcrza!aSadQK=PuTkkYj}>@#pRs zl4AuKqqah|o;q~BFuRn?goPQ&As?aQ8yng~6sY@#W diff --git a/app.py b/app.py index 809b844..7a32b77 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError import re +from html import unescape TARGET_URL = "https://m.slrclub.com/l/free" @@ -26,21 +27,93 @@ class ProxyHandler(BaseHTTPRequestHandler): charset = response.headers.get_content_charset(failobj="utf-8") raw_html = response.read().decode(charset, errors="replace") - # Rewrite relative links to use this local proxy. - if "]*>)", - rf"\1", - raw_html, - count=1, - flags=re.IGNORECASE, - ) + # Extract up to 10 post entries (제목 + 링크) and render a simple list. + items = [] + + # First try: find anchors inside list items (common for post lists) + li_blocks = re.findall(r"]*>(.*?)", raw_html, flags=re.DOTALL | re.IGNORECASE) + for block in li_blocks: + m = re.search(r"]+href=[\"']([^\"']+)[\"'][^>]*>(.*?)", block, flags=re.DOTALL | re.IGNORECASE) + if m: + href = m.group(1).strip() + anchor_html = m.group(0) + inner_html = m.group(2) + txt = re.sub(r"<[^>]+>", "", inner_html).strip() + txt = unescape(txt) + + # If inner text is empty, try anchor title or image alt attributes + if not txt: + t = re.search(r'title=["\']([^"\']+)["\']', anchor_html, flags=re.IGNORECASE) + if t: + txt = unescape(t.group(1).strip()) + + if not txt: + img_alt = re.search(r']+alt=["\']([^"\']+)["\']', anchor_html, flags=re.IGNORECASE) + if img_alt: + txt = unescape(img_alt.group(1).strip()) + + # Only append if we have a non-empty title + if txt: + items.append((href, txt)) + + # Fallback: collect anchors from whole page if no list-items found + if not items: + anchors = re.findall(r"]+href=[\"']([^\"']+)[\"'][^>]*>(.*?)", raw_html, flags=re.DOTALL | re.IGNORECASE) + for href, txt_html in anchors: + href = href.strip() + txt = re.sub(r"<[^>]+>", "", txt_html).strip() + txt = unescape(txt) + if txt and not href.lower().startswith("javascript:"): + items.append((href, txt)) + + # Deduplicate and filter out obvious navigation/header links + seen = set() + filtered = [] + for href, txt in items: + key = (href, txt) + if key in seen: + continue + seen.add(key) + lowtxt = (txt or "").strip().lower() + if href in ("#", "/", "", "/l/free"): + continue + if lowtxt in ("홈", "로그인", "검색", "공지", "more", "더보기"): + continue + filtered.append((href, txt)) + + # Skip the top 5 posts and show the next 10 + posts = filtered[5:5+10] + + # Build simple HTML list for posts only + host = self.headers.get("Host", f"localhost:{self.server.server_port}") + out_lines = [ + '', + 'SLRClub - 게시글 목록', + '

게시글 (최대 10개)

', + '
    ' + ] + for href, txt in posts: + # Build absolute URL to original site so clicks open the real page + abs_url = href + if href.startswith("/"): + abs_url = "https://m.slrclub.com" + href + elif href.startswith("//"): + abs_url = "https:" + href + elif not href.startswith("http"): + # relative path without leading slash + abs_url = "https://m.slrclub.com/" + href.lstrip("/") + + # Escape minimal characters in title + safe_txt = txt.replace("<", "<").replace(">", ">") + out_lines.append(f'
  • {safe_txt}
  • ') + + out_lines.extend(["
", ""]) + out = "\n".join(out_lines) self.send_response(200) - self.send_header("Content-Type", f"{content_type}; charset={charset}") + self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() - self.wfile.write(raw_html.encode(charset, errors="replace")) + self.wfile.write(out.encode("utf-8", errors="replace")) except HTTPError as e: self.send_response(e.code)