From f30fe695a3514bae3bdd844f4fb034516aec1293 Mon Sep 17 00:00:00 2001 From: Sakshi Kasar Date: Wed, 8 Jan 2025 10:31:56 +0530 Subject: [PATCH 1/7] sak_con --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f495082..5451e74 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# Project \ No newline at end of file +# Project +testing/sk \ No newline at end of file From 7560972fbd967b4a4d30dd53984fbe3ad9a16bea Mon Sep 17 00:00:00 2001 From: Simran Sah Date: Sun, 23 Feb 2025 00:06:39 +0530 Subject: [PATCH 2/7] github scrape first commit --- application/app/github.py | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/application/app/github.py b/application/app/github.py index e69de29..515169a 100644 --- a/application/app/github.py +++ b/application/app/github.py @@ -0,0 +1,74 @@ +import json +import requests +import base64 +import json + +def save_json(filename , json_data): + with open('{}.json'.format(filename), 'w') as fp: + json.dump(json_data, fp , indent= True) + + +def extract_data(DataNeeded, DataFromGithub, ): + Data = {} + for (k, v) in DataFromGithub.items(): + + if k in DataNeeded: + Data[k] = v + + return Data + +class User: + def __init__(self, Username): + self.Username = Username + self.UserURL = 'https://api.github.com/users/{}'.format(self.Username) + + def get_user_stats(self): + user_data = requests.get(self.UserURL).json() + data_needed = [ + 'name', + 'type', + 'company', + 'blog', + 'location', + 'email', + 'public_repos', + 'followers' + ] + self.UserData = extract_data(data_needed, user_data) + + repos_url = 'https://api.github.com/users/{}/repos'.format(self.Username) + repos_data = requests.get(repos_url).json() + + repos_list = [] + + for repo in repos_data: + repo_info = {"name": repo.get("name")} + readme_url = "https://api.github.com/repos/{}/{}/readme".format(self.Username, repo.get("name")) + readme_response = requests.get(readme_url) + if readme_response.status_code == 200: + readme_json = readme_response.json() + if "content" in readme_json and readme_json.get("encoding") == "base64": + try: + decoded_content = base64.b64decode(readme_json["content"]).decode('utf-8') + if len(decoded_content) > 5000: + decoded_content = decoded_content[:5000] + repo_info["readme"] = decoded_content + except Exception as e: + repo_info["readme"] = "Error decoding README: {}".format(e) + else: + repo_info["readme"] = None + else: + repo_info["readme"] = None + + repos_list.append(repo_info) + + self.UserData["repositories"] = repos_list + + save_json('output_of_User', self.UserData) + + return json.dumps(self.UserData, indent=4) + +# username_input = 'username' +# user = User(username_input) +# data = user.get_user_stats() +# print(data) From 92e97d59293964386cf2ae5b7c43056d6ba00fbe Mon Sep 17 00:00:00 2001 From: Simran Sah Date: Sun, 23 Feb 2025 14:24:13 +0530 Subject: [PATCH 3/7] base skeleton --- app.py | 67 +++++++++++++-- application/app/linkedin.py | 15 ++++ application/static/styles.css | 141 +++++++++++++------------------ application/templates/index.html | 100 ++++++++++++++-------- 4 files changed, 201 insertions(+), 122 deletions(-) diff --git a/app.py b/app.py index 385a9e5..01a0491 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,73 @@ from flask import Flask, render_template, request, jsonify +from fpdf import FPDF +from docx import Document +import os -app = Flask(__name__) +app = Flask( + __name__, + template_folder=os.path.join("Application", "templates"), + static_folder=os.path.join("Application", "static") +) + + +data_store = { + "linkedin": "", + "github": "", + "chat": [] +} @app.route('/') -def home(): +def index(): return render_template('index.html') +@app.route('/submit_links', methods=['POST']) +def submit_links(): + data_store["linkedin"] = request.form.get("linkedin") + data_store["github"] = request.form.get("github") + return jsonify({"message": "Links submitted successfully!"}) + @app.route('/chat', methods=['POST']) def chat(): - user_message = request.json.get('message') - response_message = f"You said: {user_message}. This is a placeholder response!" - return jsonify({"response": response_message}) + user_message = request.json.get("message") + bot_response = f"You said: {user_message}. Tell me more!" + + data_store["chat"].append({"user": user_message, "bot": bot_response}) + + return jsonify({"response": bot_response}) + +@app.route('/export', methods=['POST']) +def export(): + export_type = request.form.get("export_type") + + if export_type == "pdf": + pdf = FPDF() + pdf.add_page() + pdf.set_font("Arial", size=12) + pdf.cell(200, 10, txt="Chatbot Interaction Export", ln=True, align='C') + pdf.cell(200, 10, txt=f"LinkedIn: {data_store['linkedin']}", ln=True) + pdf.cell(200, 10, txt=f"GitHub: {data_store['github']}", ln=True) + + for conversation in data_store["chat"]: + pdf.cell(200, 10, txt=f"User: {conversation['user']}", ln=True) + pdf.cell(200, 10, txt=f"Bot: {conversation['bot']}", ln=True) + + pdf.output("chat_export.pdf") + return jsonify({"message": "Exported as PDF successfully!", "file": "chat_export.pdf"}) + + elif export_type == "word": + doc = Document() + doc.add_heading("Chatbot Interaction Export", level=1) + doc.add_paragraph(f"LinkedIn: {data_store['linkedin']}") + doc.add_paragraph(f"GitHub: {data_store['github']}") + + for conversation in data_store["chat"]: + doc.add_paragraph(f"User: {conversation['user']}") + doc.add_paragraph(f"Bot: {conversation['bot']}") + + doc.save("chat_export.docx") + return jsonify({"message": "Exported as Word successfully!", "file": "chat_export.docx"}) + + return jsonify({"message": "Invalid export type!"}) if __name__ == '__main__': app.run(debug=True) diff --git a/application/app/linkedin.py b/application/app/linkedin.py index e69de29..22d4f1e 100644 --- a/application/app/linkedin.py +++ b/application/app/linkedin.py @@ -0,0 +1,15 @@ +from linkedin_scraper import Person, actions +from selenium import webdriver +driver = webdriver.Chrome() +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +chrome_options = Options() +chrome_options.add_argument('--ignore-certificate-errors') +chrome_options.add_argument('--ignore-ssl-errors') +driver = webdriver.Chrome(options=chrome_options) + +email = "sakshikasar08@gmail.com" +password = "Nseahkas08" +actions.login(driver, email, password) +person = Person("https://www.linkedin.com/in/simransah19", driver=driver) \ No newline at end of file diff --git a/application/static/styles.css b/application/static/styles.css index ea06919..b4366eb 100644 --- a/application/static/styles.css +++ b/application/static/styles.css @@ -1,88 +1,65 @@ body { - font-family: Arial, sans-serif; - background-color: #f5f5f5; + font-family: 'Kristen ITC', sans-serif; + background-color: #fdf1e5; + color: #4a4a4a; margin: 0; padding: 0; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; -} - -.chat-container { - width: 400px; - background: #fff; - border-radius: 10px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - overflow: hidden; - display: flex; - flex-direction: column; -} - -h1 { - margin: 0; - padding: 15px; + } + + .container { + max-width: 800px; + margin: 20px auto; + padding: 20px; + background: #fbeaff; + border-radius: 20px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-align: center; - background: #4CAF50; - color: white; -} - -#chat-box { - flex: 1; - padding: 10px; - overflow-y: auto; - background: #f9f9f9; -} - -#messages { - display: flex; - flex-direction: column; - gap: 10px; -} - -.message { - max-width: 70%; - padding: 10px; - border-radius: 10px; - word-wrap: break-word; -} - -.user-message { - align-self: flex-end; - background: #d1e7dd; - color: #0f5132; -} - -.bot-message { - align-self: flex-start; - background: #f8d7da; - color: #842029; -} - -.input-area { - display: flex; - padding: 10px; - background: #e0e0e0; -} - -input[type="text"] { - flex: 1; - padding: 10px; - border: 1px solid #ccc; - border-radius: 5px; + } + + h1, h2 { + color: #ff6f91; + } + + input, button { + margin: 10px 0; + padding: 15px; + width: 90%; + border: 2px solid #ffa6c9; + border-radius: 12px; + font-size: 18px; + background-color: #ffffff; + } + + input:focus, button:hover { outline: none; -} - -button { - padding: 10px 20px; - margin-left: 10px; - border: none; - border-radius: 5px; - background: #4CAF50; - color: white; + background-color: #ffe4f7; + border-color: #ff6f91; + } + + button { + background-color: #ffb3d9; + color: #4a4a4a; + font-weight: bold; cursor: pointer; -} - -button:hover { - background: #45a049; -} + transition: background-color 0.3s ease; + } + + button:hover { + background-color: #ff6f91; + color: #ffffff; + } + + #chat-box { + border: 2px dashed #ffa6c9; + padding: 15px; + height: 300px; + overflow-y: auto; + margin: 20px 0; + background-color: #fff0f6; + border-radius: 12px; + } + + .form-group { + margin-bottom: 20px; + } + \ No newline at end of file diff --git a/application/templates/index.html b/application/templates/index.html index cc685c2..a49bab6 100644 --- a/application/templates/index.html +++ b/application/templates/index.html @@ -1,44 +1,74 @@ - - - Chatbot Interface - + + + Chatbot App + + -
-

Chatbot

-
-
-
-
- - -
+
+

Chat with JoyBot

+
+ + +
+
+ + +
+ + +
+

Chatbot

+
+ +
- +
+

Export Data

+ + +
+
From c54b131442fb77a4719b3b282bf776997eb95f79 Mon Sep 17 00:00:00 2001 From: Simran Sah Date: Sun, 23 Feb 2025 14:29:14 +0530 Subject: [PATCH 4/7] linkedin phase 1 --- .env | 2 + application/app/linkedin.py | 164 ++++++++++++++++++++++++++++++++++-- 2 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..a4e2adc --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +LINKEDIN_USERNAME=hasnarmis.work719@gmail.com +LINKEDIN_PASSWORD=disim123@ diff --git a/application/app/linkedin.py b/application/app/linkedin.py index 22d4f1e..3dcda9a 100644 --- a/application/app/linkedin.py +++ b/application/app/linkedin.py @@ -1,15 +1,161 @@ -from linkedin_scraper import Person, actions -from selenium import webdriver -driver = webdriver.Chrome() +import os +import json +import time +from dotenv import load_dotenv from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException from selenium.webdriver.chrome.options import Options chrome_options = Options() -chrome_options.add_argument('--ignore-certificate-errors') -chrome_options.add_argument('--ignore-ssl-errors') +chrome_options.add_argument("--disable-gpu") +# chrome_options.add_argument("--headless") +chrome_options.add_argument("--no-sandbox") + driver = webdriver.Chrome(options=chrome_options) -email = "sakshikasar08@gmail.com" -password = "Nseahkas08" -actions.login(driver, email, password) -person = Person("https://www.linkedin.com/in/simransah19", driver=driver) \ No newline at end of file +load_dotenv() +LINKEDIN_USERNAME = os.getenv('LINKEDIN_USERNAME') +LINKEDIN_PASSWORD = os.getenv('LINKEDIN_PASSWORD') + +def init_driver(): + """Initialize Selenium ChromeDriver with options.""" + options = webdriver.ChromeOptions() + # options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + driver = webdriver.Chrome(options=options) + driver.maximize_window() + return driver + +def linkedin_login(driver): + """Logs into LinkedIn using credentials from environment variables.""" + driver.get("https://www.linkedin.com/login") + WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "username"))) + + driver.find_element(By.ID, "username").send_keys(LINKEDIN_USERNAME) + driver.find_element(By.ID, "password").send_keys(LINKEDIN_PASSWORD) + driver.find_element(By.XPATH, "//button[@type='submit']").click() + + WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "global-nav-search"))) + +def scrape_linkedin_profile(driver, profile_url): + """Scrapes LinkedIn profile data and returns a dictionary.""" + data = {} + driver.get(profile_url) + time.sleep(5) + + try: + name_element = driver.find_element(By.CSS_SELECTOR, "h1.text-heading-xlarge") + data['name'] = name_element.text + except NoSuchElementException: + data['name'] = None + + try: + company_element = driver.find_element(By.XPATH, "//div[contains(@class, 'pv-entity__secondary-title')]") + data['company'] = company_element.text.strip() + except NoSuchElementException: + data['company'] = None + + try: + education_section = driver.find_element(By.ID, "education-section") + education_items = education_section.find_elements(By.CSS_SELECTOR, "li") + education_list = [item.text for item in education_items] + data['education'] = education_list + except NoSuchElementException: + data['education'] = [] + + try: + licenses_section = driver.find_element(By.ID, "licenses-certifications-section") + license_items = licenses_section.find_elements(By.CSS_SELECTOR, "li") + licenses_list = [item.text for item in license_items] + data['licenses_certifications'] = licenses_list + except NoSuchElementException: + data['licenses_certifications'] = [] + + try: + projects_section = driver.find_element(By.ID, "projects-section") + project_items = projects_section.find_elements(By.CSS_SELECTOR, "li") + projects_list = [item.text for item in project_items] + data['projects'] = projects_list + except NoSuchElementException: + data['projects'] = [] + + try: + volunteering_section = driver.find_element(By.ID, "volunteering-section") + volunteering_items = volunteering_section.find_elements(By.CSS_SELECTOR, "li") + volunteering_list = [item.text for item in volunteering_items] + data['volunteering'] = volunteering_list + except NoSuchElementException: + data['volunteering'] = [] + + try: + skills_section = driver.find_element(By.ID, "skills-section") + skill_items = skills_section.find_elements(By.CSS_SELECTOR, "li") + skills_list = [item.text for item in skill_items] + data['skills'] = skills_list + except NoSuchElementException: + data['skills'] = [] + + try: + recommendations_section = driver.find_element(By.ID, "recommendations-section") + recommendation_items = recommendations_section.find_elements(By.CSS_SELECTOR, "li") + recommendations_list = [item.text for item in recommendation_items] + data['recommendations'] = recommendations_list + except NoSuchElementException: + data['recommendations'] = [] + + try: + honors_section = driver.find_element(By.ID, "honors-awards-section") + honors_items = honors_section.find_elements(By.CSS_SELECTOR, "li") + honors_list = [item.text for item in honors_items] + data['honors_awards'] = honors_list + except NoSuchElementException: + data['honors_awards'] = [] + + try: + posts_tab = driver.find_element(By.XPATH, "//a[contains(@href, '/detail/recent-activity/')]") + posts_tab.click() + time.sleep(5) # Wait for posts to load + posts = driver.find_elements(By.CSS_SELECTOR, "div.feed-shared-update-v2") + posts_captions = [] + for post in posts: + try: + caption = post.find_element(By.CSS_SELECTOR, "span.break-words").text + posts_captions.append(caption) + except NoSuchElementException: + continue + data['posts_captions'] = posts_captions + except NoSuchElementException: + data['posts_captions'] = [] + + return data + +def save_data(data, json_filename="linkedin_data.json", txt_filename="linkedin_data.txt"): + """Saves the scraped data to JSON and TXT files.""" + with open(json_filename, "w", encoding="utf-8") as json_file: + json.dump(data, json_file, indent=4, ensure_ascii=False) + + with open(txt_filename, "w", encoding="utf-8") as txt_file: + for key, value in data.items(): + txt_file.write(f"{key}:\n{value}\n\n") + +if __name__ == "__main__": + # profile_url = input("Enter the LinkedIn profile URL to scrape: ") + # Testing with sans profile + profile_url ="https://www.linkedin.com/in/sanskriti-joshi-408575266/" + driver = init_driver() + try: + linkedin_login(driver) + time.sleep(3) + scraped_data = scrape_linkedin_profile(driver, profile_url) + save_data(scraped_data) + print("Scraping complete. Data saved in 'linkedin_data.json' and 'linkedin_data.txt'.") + finally: + try: + driver.quit() + except OSError as e: + print("Error terminating service process (ignored):", e) + From 22e4da24bcc65bccd73f08264ed451cc775c7ea4 Mon Sep 17 00:00:00 2001 From: Simran Sah Date: Sun, 23 Feb 2025 14:31:04 +0530 Subject: [PATCH 5/7] ignore .env --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d75edea..771ba65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv -__pycache__ \ No newline at end of file +__pycache__ +.env \ No newline at end of file From c0c11b5925caef5a2e3c3eec45bdaebc4d0891c2 Mon Sep 17 00:00:00 2001 From: SIMRAN SAH <106486630+SIMRAN719@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:32:59 +0530 Subject: [PATCH 6/7] Delete .env --- .env | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index a4e2adc..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -LINKEDIN_USERNAME=hasnarmis.work719@gmail.com -LINKEDIN_PASSWORD=disim123@ From 96b41fe197a26749471ef6dbc88d2c48139b8f38 Mon Sep 17 00:00:00 2001 From: Sakshi Kasar Date: Sun, 23 Feb 2025 23:18:34 +0530 Subject: [PATCH 7/7] Updated styles and index page, added SPARC file --- SPARC | 1 + application/static/styles.css | 257 ++++++++++++++++++++++++------- application/templates/index.html | 126 +++++++-------- 3 files changed, 264 insertions(+), 120 deletions(-) create mode 160000 SPARC diff --git a/SPARC b/SPARC new file mode 160000 index 0000000..f30fe69 --- /dev/null +++ b/SPARC @@ -0,0 +1 @@ +Subproject commit f30fe695a3514bae3bdd844f4fb034516aec1293 diff --git a/application/static/styles.css b/application/static/styles.css index b4366eb..2a7a560 100644 --- a/application/static/styles.css +++ b/application/static/styles.css @@ -1,65 +1,206 @@ body { - font-family: 'Kristen ITC', sans-serif; - background-color: #fdf1e5; - color: #4a4a4a; - margin: 0; - padding: 0; - } - - .container { - max-width: 800px; - margin: 20px auto; - padding: 20px; - background: #fbeaff; - border-radius: 20px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; + background: linear-gradient(to bottom, #190019, #2B124C, #522B5B, #654F6C, #DFB6B2, #FBE4D8); + color: #FBE4D8; + +} +h1, h2 { + font-family: 'Pacifico', cursive; + font-size: 36px; + color: #FBE4D8; text-align: center; - } - - h1, h2 { - color: #ff6f91; - } - - input, button { - margin: 10px 0; - padding: 15px; - width: 90%; - border: 2px solid #ffa6c9; - border-radius: 12px; - font-size: 18px; - background-color: #ffffff; - } +} +.container { + width: 100%; + height: 100%; + padding: 20px; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; +} + +.header { + display: flex; + align-items: center; + margin-bottom: 50px; + justify-content: center; + width: 70%; +} + +.search-bar { + height: 5px; + width: 100%; + padding: 20px; + font-size: 50px; + font-family:'Playfair Display'; + background-color: #DFB6B2; + color: #190019; + border: 1px solid; + border-radius: 5px; + margin-left: 300px; - input:focus, button:hover { - outline: none; - background-color: #ffe4f7; - border-color: #ff6f91; - } +} + +.search-bar:hover { + outline: none; + background-color: #654F6C; + border-color: #522B5B; +} + +.settings-button { + height: 29%; + width: 100%; + background: none; + border: none; + font-size: 50px; + cursor: pointer; + margin-left: 100px; + color: #FBE4D8; +} + +.content { + display: flex; + gap: 3px; + width: 80%; + font-family:'Playfair Display'; + justify-content: center; - button { - background-color: #ffb3d9; - color: #4a4a4a; - font-weight: bold; - cursor: pointer; - transition: background-color 0.3s ease; - } +} + +.left-panel { + display: flex; + flex-direction: column; + font-family: 'Playfair Display'; + gap: 10px; + width: 20%; - button:hover { - background-color: #ff6f91; - color: #ffffff; - } +} + +.file-button { + padding: 10px; + border: none; + background-color: #FBE4D8; + color: #190019; + border-radius: 5px; + cursor: pointer; + font-family: 'Playfair Display'; + font-size: 14px; + width: 70%; +} + +.main-panel { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 10px; + width: 60%; + margin-left: 1px; + align-items: center; - #chat-box { - border: 2px dashed #ffa6c9; - padding: 15px; - height: 300px; - overflow-y: auto; - margin: 20px 0; - background-color: #fff0f6; - border-radius: 12px; - } +} +#link{ + background-color: #FFF6E9; + font-family: 'Playfair Display'; +} + +.chat-panel { + display: flex; + align-items: center; + gap: 10px; + width: 70%; + color: white; +} + +.input-box, .chat-input { + padding: 12px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; + width: 70%; + background-color: #522B5B; + color: white; +} + +.submit-button { + padding: 12px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; + width: 40%; +} + +.send-button{ + padding: 12px; + border: 1px solid #ccc; + border-radius: 25px; + font-size: 16px; + width: 40%; +} +#chat-section { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 80%; + border-radius: 25px; + background-color: #522B5B; + color: #FBE4D8; +} + +#chat-box { + display: flex; + flex-direction: row; + border: 1px solid #ccc; + padding: 15px; + height: 200px; + font-family: 'Playfair Display'; + width: 70%; + overflow-y: auto; + background-color: #522B5B; + color: #FBE4D8; + border-radius: 25px; + margin-bottom: 25px; + margin-top: 80px; +} + +.chat-controls { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + width: 70%; +} + +.chat-input { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; - .form-group { - margin-bottom: 20px; - } - \ No newline at end of file +} + +.button-group { + display: flex; + justify-content: space-between; + width: 100%; +} + +.send-button { + padding: 10px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + width: auto; + background-color: #DFB6B2; + color: #190019; +} diff --git a/application/templates/index.html b/application/templates/index.html index a49bab6..ade5ef7 100644 --- a/application/templates/index.html +++ b/application/templates/index.html @@ -1,74 +1,76 @@ - - - Chatbot App - - + async function exportData(type) { + const response = await fetch("/export", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ export_type: type }), + }); + const data = await response.json(); + alert(data.message); + } + -
-

Chat with JoyBot

-
- - -
-
- - -
- - -
-

Chatbot

-
- - -
-
-

Export Data

- - +
+

Chat with SPARC

+
+ + +
+
+
+ + + +
+
+ + + +
+
+
+
+ + +
+
+
-