diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc
index b656a39..7605a0c 100644
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
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)