gmail.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import hashlib
  2. import logging
  3. import os
  4. import quopri
  5. from textwrap import dedent
  6. from bs4 import BeautifulSoup
  7. try:
  8. from llama_hub.gmail.base import GmailReader
  9. except ImportError:
  10. raise ImportError("Gmail requires extra dependencies. Install with `pip install embedchain[gmail]`") from None
  11. from embedchain.loaders.base_loader import BaseLoader
  12. from embedchain.utils import clean_string
  13. def get_header(text: str, header: str) -> str:
  14. start_string_position = text.find(header)
  15. pos_start = text.find(":", start_string_position) + 1
  16. pos_end = text.find("\n", pos_start)
  17. header = text[pos_start:pos_end]
  18. return header.strip()
  19. class GmailLoader(BaseLoader):
  20. def load_data(self, query):
  21. """Load data from gmail."""
  22. if not os.path.isfile("credentials.json"):
  23. raise FileNotFoundError(
  24. "You must download the valid credentials file from your google \
  25. dev account. Refer this `https://cloud.google.com/docs/authentication/api-keys`"
  26. )
  27. loader = GmailReader(query=query, service=None, results_per_page=20)
  28. documents = loader.load_data()
  29. logging.info(f"Gmail Loader: {len(documents)} mails found for query- {query}")
  30. data = []
  31. data_contents = []
  32. logging.info(f"Gmail Loader: {len(documents)} mails found")
  33. for document in documents:
  34. original_size = len(document.text)
  35. snippet = document.metadata.get("snippet")
  36. meta_data = {
  37. "url": document.metadata.get("id"),
  38. "date": get_header(document.text, "Date"),
  39. "subject": get_header(document.text, "Subject"),
  40. "from": get_header(document.text, "From"),
  41. "to": get_header(document.text, "To"),
  42. "search_query": query,
  43. }
  44. # Decode
  45. decoded_bytes = quopri.decodestring(document.text)
  46. decoded_str = decoded_bytes.decode("utf-8", errors="replace")
  47. # Slice
  48. mail_start = decoded_str.find("<!DOCTYPE")
  49. email_data = decoded_str[mail_start:]
  50. # Web Page HTML Processing
  51. soup = BeautifulSoup(email_data, "html.parser")
  52. tags_to_exclude = [
  53. "nav",
  54. "aside",
  55. "form",
  56. "header",
  57. "noscript",
  58. "svg",
  59. "canvas",
  60. "footer",
  61. "script",
  62. "style",
  63. ]
  64. for tag in soup(tags_to_exclude):
  65. tag.decompose()
  66. ids_to_exclude = ["sidebar", "main-navigation", "menu-main-menu"]
  67. for id in ids_to_exclude:
  68. tags = soup.find_all(id=id)
  69. for tag in tags:
  70. tag.decompose()
  71. classes_to_exclude = [
  72. "elementor-location-header",
  73. "navbar-header",
  74. "nav",
  75. "header-sidebar-wrapper",
  76. "blog-sidebar-wrapper",
  77. "related-posts",
  78. ]
  79. for class_name in classes_to_exclude:
  80. tags = soup.find_all(class_=class_name)
  81. for tag in tags:
  82. tag.decompose()
  83. content = soup.get_text()
  84. content = clean_string(content)
  85. cleaned_size = len(content)
  86. if original_size != 0:
  87. logging.info(
  88. f"[{id}] Cleaned page size: {cleaned_size} characters, down from {original_size} (shrunk: {original_size-cleaned_size} chars, {round((1-(cleaned_size/original_size)) * 100, 2)}%)" # noqa:E501
  89. )
  90. result = f"""
  91. email from '{meta_data.get('from')}' to '{meta_data.get('to')}'
  92. subject: {meta_data.get('subject')}
  93. date: {meta_data.get('date')}
  94. preview: {snippet}
  95. content: f{content}
  96. """
  97. data_content = dedent(result)
  98. data.append({"content": data_content, "meta_data": meta_data})
  99. data_contents.append(data_content)
  100. doc_id = hashlib.sha256((query + ", ".join(data_contents)).encode()).hexdigest()
  101. response_data = {"doc_id": doc_id, "data": data}
  102. return response_data