Ivan000 commited on
Commit
8893ef9
·
verified ·
1 Parent(s): 40d72d0

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +43 -27
main.py CHANGED
@@ -3,8 +3,6 @@
3
  from fastapi import FastAPI, Query
4
  from fastapi.responses import Response
5
  import requests
6
- from bs4 import BeautifulSoup
7
- from urllib.parse import quote
8
  import xml.etree.ElementTree as ET
9
  from datetime import datetime
10
  from typing import Optional
@@ -12,6 +10,9 @@ from typing import Optional
12
  app = FastAPI()
13
 
14
  # ========== FB2 Generator ==========
 
 
 
15
  def html_to_fb2(title: str, body: str) -> str:
16
  clean_text = BeautifulSoup(body, "html.parser").get_text(separator="\n")
17
  return f"""<?xml version='1.0' encoding='utf-8'?>
@@ -32,28 +33,30 @@ def html_to_fb2(title: str, body: str) -> str:
32
  </body>
33
  </FictionBook>"""
34
 
35
- # ========== DuckDuckGo Search ==========
36
  def duckduckgo_search(query: str):
37
- res = requests.post(
38
- "https://html.duckduckgo.com/html/",
39
- data={"q": query},
40
- headers={"User-Agent": "Mozilla/5.0"},
41
- timeout=10
42
- )
 
 
43
  res.raise_for_status()
44
- soup = BeautifulSoup(res.text, "html.parser")
45
  results = []
46
- for a in soup.select("a.result__a"):
47
- href = a.get("href")
48
- title = a.get_text()
49
- if href and title:
50
- results.append((title.strip(), href))
51
- if len(results) >= 10:
52
- break
53
- return results
54
 
55
- # ========== OPDS Feed Generation ==========
56
- def create_feed(entries: list, templated: bool, q: Optional[str]) -> bytes:
57
  ns = "http://www.w3.org/2005/Atom"
58
  ET.register_namespace("", ns)
59
  feed = ET.Element("feed", xmlns=ns)
@@ -62,15 +65,14 @@ def create_feed(entries: list, templated: bool, q: Optional[str]) -> bytes:
62
  ET.SubElement(feed, "updated").text = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
63
 
64
  # OpenSearch templated search link
65
- link_attrs = {
66
  "rel": "search",
67
  "type": "application/atom+xml;profile=opds-catalog;kind=search",
68
  "href": "/opds?q={searchTerms}",
69
  "templated": "true"
70
- }
71
- ET.SubElement(feed, "link", link_attrs)
72
 
73
- # Add entries for search results
74
  for entry_info in entries:
75
  entry = ET.SubElement(feed, "entry")
76
  ET.SubElement(entry, "id").text = entry_info['id']
@@ -81,8 +83,8 @@ def create_feed(entries: list, templated: bool, q: Optional[str]) -> bytes:
81
  return ET.tostring(feed, encoding="utf-8", xml_declaration=True)
82
 
83
  # ========== Routes ==========
84
- @app.get("/opds", include_in_schema=False)
85
- def opds(q: Optional[str] = Query(None, alias="q", description="Search query")) -> Response:
86
  entries = []
87
  kind = "search"
88
  if q:
@@ -99,7 +101,7 @@ def opds(q: Optional[str] = Query(None, alias="q", description="Search query"))
99
  }
100
  })
101
  kind = "acquisition"
102
- xml_data = create_feed(entries, templated=True, q=q)
103
  return Response(content=xml_data,
104
  media_type=f"application/atom+xml;profile=opds-catalog;kind={kind}")
105
 
@@ -107,6 +109,7 @@ def opds(q: Optional[str] = Query(None, alias="q", description="Search query"))
107
  def download_fb2(url: str) -> Response:
108
  res = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
109
  res.raise_for_status()
 
110
  soup = BeautifulSoup(res.text, "html.parser")
111
  title = soup.title.string.strip() if soup.title and soup.title.string else "article"
112
  fb2 = html_to_fb2(title, str(soup.body))
@@ -117,3 +120,16 @@ def download_fb2(url: str) -> Response:
117
  headers={"Content-Disposition": f"attachment; filename={filename}"}
118
  )
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  from fastapi import FastAPI, Query
4
  from fastapi.responses import Response
5
  import requests
 
 
6
  import xml.etree.ElementTree as ET
7
  from datetime import datetime
8
  from typing import Optional
 
10
  app = FastAPI()
11
 
12
  # ========== FB2 Generator ==========
13
+ from bs4 import BeautifulSoup
14
+ from urllib.parse import quote
15
+
16
  def html_to_fb2(title: str, body: str) -> str:
17
  clean_text = BeautifulSoup(body, "html.parser").get_text(separator="\n")
18
  return f"""<?xml version='1.0' encoding='utf-8'?>
 
33
  </body>
34
  </FictionBook>"""
35
 
36
+ # ========== DuckDuckGo JSON Search ==========
37
  def duckduckgo_search(query: str):
38
+ api_url = "https://api.duckduckgo.com/"
39
+ params = {
40
+ "q": query,
41
+ "format": "json",
42
+ "no_html": 1,
43
+ "skip_disambig": 1
44
+ }
45
+ res = requests.get(api_url, params=params, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
46
  res.raise_for_status()
47
+ data = res.json()
48
  results = []
49
+ def extract_topics(topics):
50
+ for item in topics:
51
+ if "FirstURL" in item and "Text" in item:
52
+ results.append((item["Text"], item["FirstURL"]))
53
+ elif "Topics" in item:
54
+ extract_topics(item["Topics"])
55
+ extract_topics(data.get("RelatedTopics", []))
56
+ return results[:10]
57
 
58
+ # ========== OPDS Feed Generator ==========
59
+ def create_feed(entries: list, q: Optional[str]) -> bytes:
60
  ns = "http://www.w3.org/2005/Atom"
61
  ET.register_namespace("", ns)
62
  feed = ET.Element("feed", xmlns=ns)
 
65
  ET.SubElement(feed, "updated").text = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
66
 
67
  # OpenSearch templated search link
68
+ ET.SubElement(feed, "link", {
69
  "rel": "search",
70
  "type": "application/atom+xml;profile=opds-catalog;kind=search",
71
  "href": "/opds?q={searchTerms}",
72
  "templated": "true"
73
+ })
 
74
 
75
+ # Add entries
76
  for entry_info in entries:
77
  entry = ET.SubElement(feed, "entry")
78
  ET.SubElement(entry, "id").text = entry_info['id']
 
83
  return ET.tostring(feed, encoding="utf-8", xml_declaration=True)
84
 
85
  # ========== Routes ==========
86
+ @app.get("/opds")
87
+ def opds(q: Optional[str] = Query(None, description="Search query")) -> Response:
88
  entries = []
89
  kind = "search"
90
  if q:
 
101
  }
102
  })
103
  kind = "acquisition"
104
+ xml_data = create_feed(entries, q)
105
  return Response(content=xml_data,
106
  media_type=f"application/atom+xml;profile=opds-catalog;kind={kind}")
107
 
 
109
  def download_fb2(url: str) -> Response:
110
  res = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
111
  res.raise_for_status()
112
+ from bs4 import BeautifulSoup
113
  soup = BeautifulSoup(res.text, "html.parser")
114
  title = soup.title.string.strip() if soup.title and soup.title.string else "article"
115
  fb2 = html_to_fb2(title, str(soup.body))
 
120
  headers={"Content-Disposition": f"attachment; filename={filename}"}
121
  )
122
 
123
+ # File: Dockerfile
124
+ FROM python:3.11-slim
125
+ WORKDIR /app
126
+ COPY requirements.txt .
127
+ RUN pip install --no-cache-dir -r requirements.txt
128
+ COPY main.py .
129
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
130
+
131
+ # File: requirements.txt
132
+ fastapi
133
+ uvicorn
134
+ requests
135
+ beautifulsoup4