web_reader_tool.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import mimetypes
  2. import re
  3. from collections.abc import Sequence
  4. from dataclasses import dataclass
  5. from typing import Any, Optional, cast
  6. from urllib.parse import unquote
  7. import chardet
  8. import cloudscraper # type: ignore
  9. from readabilipy import simple_json_from_html_string # type: ignore
  10. from core.helper import ssrf_proxy
  11. from core.rag.extractor import extract_processor
  12. from core.rag.extractor.extract_processor import ExtractProcessor
  13. FULL_TEMPLATE = """
  14. TITLE: {title}
  15. AUTHOR: {author}
  16. TEXT:
  17. {text}
  18. """
  19. def page_result(text: str, cursor: int, max_length: int) -> str:
  20. """Page through `text` and return a substring of `max_length` characters starting from `cursor`."""
  21. return text[cursor : cursor + max_length]
  22. def get_url(url: str, user_agent: Optional[str] = None) -> str:
  23. """Fetch URL and return the contents as a string."""
  24. headers = {
  25. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
  26. " Chrome/91.0.4472.124 Safari/537.36"
  27. }
  28. if user_agent:
  29. headers["User-Agent"] = user_agent
  30. main_content_type = None
  31. supported_content_types = extract_processor.SUPPORT_URL_CONTENT_TYPES + ["text/html"]
  32. response = ssrf_proxy.head(url, headers=headers, follow_redirects=True, timeout=(5, 10))
  33. if response.status_code == 200:
  34. # check content-type
  35. content_type = response.headers.get("Content-Type")
  36. if content_type:
  37. main_content_type = response.headers.get("Content-Type").split(";")[0].strip()
  38. else:
  39. content_disposition = response.headers.get("Content-Disposition", "")
  40. filename_match = re.search(r'filename="([^"]+)"', content_disposition)
  41. if filename_match:
  42. filename = unquote(filename_match.group(1))
  43. extension = re.search(r"\.(\w+)$", filename)
  44. if extension:
  45. main_content_type = mimetypes.guess_type(filename)[0]
  46. if main_content_type not in supported_content_types:
  47. return f"Unsupported content-type [{main_content_type}] of URL."
  48. if main_content_type in extract_processor.SUPPORT_URL_CONTENT_TYPES:
  49. return cast(str, ExtractProcessor.load_from_url(url, return_text=True))
  50. response = ssrf_proxy.get(url, headers=headers, follow_redirects=True, timeout=(120, 300))
  51. elif response.status_code == 403:
  52. scraper = cloudscraper.create_scraper()
  53. scraper.perform_request = ssrf_proxy.make_request # type: ignore
  54. response = scraper.get(url, headers=headers, follow_redirects=True, timeout=(120, 300)) # type: ignore
  55. if response.status_code != 200:
  56. return f"URL returned status code {response.status_code}."
  57. # Detect encoding using chardet
  58. detected_encoding = chardet.detect(response.content)
  59. encoding = detected_encoding["encoding"]
  60. if encoding:
  61. try:
  62. content = response.content.decode(encoding)
  63. except (UnicodeDecodeError, TypeError):
  64. content = response.text
  65. else:
  66. content = response.text
  67. article = extract_using_readabilipy(content)
  68. if not article.text:
  69. return ""
  70. res = FULL_TEMPLATE.format(
  71. title=article.title,
  72. author=article.author,
  73. text=article.text,
  74. )
  75. return res
  76. @dataclass
  77. class Article:
  78. title: str
  79. author: str
  80. text: Sequence[dict]
  81. def extract_using_readabilipy(html: str):
  82. json_article: dict[str, Any] = simple_json_from_html_string(html, use_readability=True)
  83. article = Article(
  84. title=json_article.get("title") or "",
  85. author=json_article.get("byline") or "",
  86. text=json_article.get("plain_text") or [],
  87. )
  88. return article
  89. def get_image_upload_file_ids(content):
  90. pattern = r"!\[image\]\((https?://.*?(file-preview|image-preview))\)"
  91. matches = re.findall(pattern, content)
  92. image_upload_file_ids = []
  93. for match in matches:
  94. if match[1] == "file-preview":
  95. content_pattern = r"files/([^/]+)/file-preview"
  96. else:
  97. content_pattern = r"files/([^/]+)/image-preview"
  98. content_match = re.search(content_pattern, match[0])
  99. if content_match:
  100. image_upload_file_id = content_match.group(1)
  101. image_upload_file_ids.append(image_upload_file_id)
  102. return image_upload_file_ids