diff --git a/.gitignore b/.gitignore index 70b74b64..bc402e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ _site node_modules tools/__pycache__ tools/venv +tools/files/meetup.ics bin/ lib/ .venv diff --git a/_data/events.yml b/_data/events.yml index 6e94471a..6d64b1ba 100644 --- a/_data/events.yml +++ b/_data/events.yml @@ -963,14 +963,14 @@ target: _target - title: | - LeetCode Summer Bootcamp (Weekly) + LeetCode Summer Bootcamp Week 2 description: | Our intensive Summer Coding Bootcamp: From Zero to Hero! is designed to equip you with the essential knowledge and problem-solving techniques to confidently tackle even the most challenging coding interviews. Running every week for 12 weeks! category_style: tech-talk category_name: Tech Talk date: SAT, JUN 7, 2025 - expiration: "20250824" + expiration: "20250607" host: "Irina Kamalova, Lilia Rafikova and Nonna Shakhova" speaker: "" time: 10:00 AM BST @@ -1022,3 +1022,76 @@ path: https://www.meetup.com/women-coding-community/events/307885337/ title: View meetup event target: _target + +- title: "Mentorship Check-In | How is Your Journey Going? (WCC Mentorship Programme 2025)" + description: | + How is your mentorship journey going so far? Let's check in! Join the Women Coding Community Mentorship Programme Team for a reflective and energizing Mentorship Check-In designed to reconnect, share progress, and strengthen our community halfway through the 2025 program. + category_style: tech-talk + category_name: Tech Talk + date: TUE, JUL 08, 2025 + expiration: "20250708" + host: "Idayat Sanni and Rajashree Munoli" + speaker: "" + time: 08:00 PM BST + image: + path: "https://secure.meetupstatic.com/photos/event/e/7/f/3/600_528419379.jpeg" + alt: WCC Meetup event image + link: + path: https://www.meetup.com/women-coding-community/events/308368038/ + title: View meetup event + target: _target + +- title: | + Book Club: Fundamentals of Data Engineering + description: | + About the Book "Data engineering has grown rapidly in the past decade, leaving many software engineers, data scientists, and analysts looking for a comprehensive view of this practice. + category_style: book-club + category_name: Book Club + date: MON, JUN 30, 2025 + expiration: "20250630" + host: "Silke Nodwell and Prabha Venkatesh" + speaker: "Lasha Dolenjashvili" + time: 07:00 PM BST + image: + path: "https://secure.meetupstatic.com/photos/event/1/f/c/1/600_528368129.jpeg" + alt: WCC Meetup event image + link: + path: https://www.meetup.com/women-coding-community/events/307781742/ + title: View meetup event + target: _target + +- title: LeetCode Summer Bootcamp Week 3 + description: | + Do you always feel frustrated by upcoming coding interviews? Do you feel overwhelmed by opening books and platforms to practice coding questions while trying to look at too many topics simultaneously? Our intensive Summer Coding Bootcamp: From Zero to Hero! is designed to equip you with the essential knowledge and problem-solving techniques to confidently tackle even the most challenging coding interviews. + category_style: tech-talk + category_name: Tech Talk + date: SAT, JUN 21, 2025 + expiration: "20250621" + host: "Irina Kamalova, Lilia Rafikova and Nonna Shakhova" + speaker: "" + time: 10:00 AM BST + image: + path: "https://secure.meetupstatic.com/photos/event/e/a/2/8/600_528239944.jpeg" + alt: WCC Meetup event image + link: + path: https://www.meetup.com/women-coding-community/events/308136053/ + title: View meetup event + target: _target + +- title: "Defining System Requirements \u2014 A System Design Deep Dive (Part 2 of 2)\n" + description: | + Designing robust, scalable systems starts with one essential step: defining system requirements. + category_style: tech-talk + category_name: Tech Talk + date: SUN, JUN 15, 2025 + expiration: "20250615" + host: "" + speaker: "Shailaja Koppu, Liliiia Rafikova" + time: 06:00 PM BST + image: + path: "https://secure.meetupstatic.com/photos/event/5/7/3/6/600_528502326.jpeg" + alt: WCC Meetup event image + link: + path: https://www.meetup.com/women-coding-community/events/308462205/ + title: View meetup event + target: _target diff --git a/assets/images/events/default.jpg b/assets/images/events/default.jpg new file mode 100644 index 00000000..ea077f47 Binary files /dev/null and b/assets/images/events/default.jpg differ diff --git a/tools/README.md b/tools/README.md index c4c31388..6c1f332b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -35,12 +35,17 @@ sh run_download_automation.sh ``` #### C) `meetup_import.py` +**Before running the script, make sure** to download the most recent iCal feed using [this link](https://www.meetup.com/women-coding-community/events/ical/). + +Place the downloaded `.ics` file inside the `tools/files` folder and make sure it is renamed to `meetup.ics`. + +Afterwards, run the command below: ```shell sh run_meetup_import.sh ``` **Note:** -- New data will be imported to [`imported_events.yml`](../_data/imported_events.yml) +- New data will be imported to [`imported_events.yml`](../_data/imported_events.yml). Verify that all events details are formatted correctly, manually update if needed. - Ensure to copy the generated data to [`events.yml`](../_data/events.yml) and clear the file. diff --git a/tools/files/instruction.txt b/tools/files/instruction.txt new file mode 100644 index 00000000..eac0f4aa --- /dev/null +++ b/tools/files/instruction.txt @@ -0,0 +1 @@ +Place the `meetup.ics` file in this folder \ No newline at end of file diff --git a/tools/meetup_import.py b/tools/meetup_import.py index bc14a69f..ac6de777 100644 --- a/tools/meetup_import.py +++ b/tools/meetup_import.py @@ -1,332 +1,292 @@ -import logging -import sys -from datetime import datetime -from enum import Enum -from typing import Optional, Union -from urllib.request import urlretrieve - -import requests -import yaml -from bs4 import BeautifulSoup, Tag -from pydantic import BaseModel - -CODING_CLUB_BANNER = "/assets/images/events/event-coding-club-3.jpg" -WRITING_CLUB_BANNER = "/assets/images/events/event-writing-club.jpeg" - - -class LiteralString(str): - pass - - -class QuotedString(str): - pass - - -class NoQuoteString(str): - pass - - -def literal_string_representer(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') - - -def double_quote_representer(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"') - - -def no_quote(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='') - - -yaml.add_representer(LiteralString, literal_string_representer) -yaml.add_representer(QuotedString, double_quote_representer) -yaml.add_representer(NoQuoteString, no_quote) - - -def to_literal_str(data: str) -> Union[LiteralString, str]: - special_characters = "!@#$%^&*()-+?_=,<>/:" - - # Pass the string in regex_search - if data and ("\n" in data or any(c in special_characters for c in data)): - data = data.rstrip() - data = f"{data}\n" - return LiteralString(data) - return data - - -def to_quoted_str(data: str) -> Union[QuotedString, NoQuoteString]: - special_characters = "!@#$%^&*()-+?_=,<>/:" - if data and ("\n" in data or any(c in special_characters for c in data)): - return QuotedString(data) - return NoQuoteString(data) - - -class WriteMode(Enum): - # Create new a file - WRITE = "w" - # Append in existent yml file - APPEND = "a" - - -class Image(BaseModel): - path: str = "/assets/images/events/default.jpg" - alt: str = "Square poster of event" - - -class WebLink(BaseModel): - path: Optional[str] - title: str = "View meetup event" - target: str = "_target" - - -class MeetupEvents(BaseModel): - title: str - description: str - category_style: Optional[str] = "tech-talk" - category_name: Optional[str] = "Tech Talk" - date: str - expiration: Optional[str] = "" - host: Optional[str] = "" - speaker: Optional[str] = "" - time: Optional[str] = "" - image: Optional[Image] - link: Optional[WebLink] - - -def download_image(image_url: str, description: str, category_style: str, expiration: str) -> str: - """ - Downloads an image from the given URL and saves it to the '/assets' folder. - - :param image_url: The URL of the image to download. - :return: The path of the downloaded image. - """ - image_path = f"/assets/images/events/{category_style}-{expiration}.webp" - if description: - if "coding club" in description.lower(): - image_path = CODING_CLUB_BANNER - elif "writing club" in description.lower(): - image_path = WRITING_CLUB_BANNER - else: - try: - urlretrieve(image_url, f"..{image_path}") - except Exception as e: - logging.error(f"Error downloading image from '{image_url}': {e}") - image_path = "/assets/images/events/default.jpg" - return image_path - - -def get_upcoming_meetups(url: str) -> list[MeetupEvents]: - """ - This function scrapes the given Meetup.com webpage URL to extract upcoming meetup info. - - :param url: The URL of the Meetup.com group page. - :return: A list of dictionaries with details about the upcoming meetups (name, date, time, etc.). - """ - response = requests.get(url) - soup = BeautifulSoup(response.content, "html.parser") - - upcoming_meetups: list[MeetupEvents] = [] - - # Find all upcoming meetup listings - upcoming_listings = soup.find_all("div", class_="rounded-md bg-white p-4 shadow-sm sm:p-5") - - for listing in upcoming_listings: - # Reset host, speaker, date, etc., for each new listing - host: str = "" - speaker: str = "" - date: str = "" - time: str = "" - expiration: str = "" - # Find the title and description elements once and reuse them - title = listing.find("span", - class_="ds-font-title-3 block break-words leading-7 utils_cardTitle__sAAHG").text.strip() - listing.find_all_next("div", class_="flex items-start space-x-1.5") - # Find all description elements - description_elements = listing.findAll("p", class_="mb-4") - # Get the first description element - description = description_elements[0].text.strip() - # if len(description_elements) > 1: - # description_element = description_elements[1].text.strip() - for description_div in description_elements: - if description_div.text.startswith("Host:"): - host = extract_name(description_div) - if description_div.text.lower().startswith("co-host:"): - host = f'{host} and {extract_name(description_div)}' - if description_div.text.startswith("Speaker:"): - speaker = extract_name(description_div) - - time_element = listing.find("time", class_="text-[#00829B] text-sm font-medium uppercase").text - if time_element: - date = ",".join(time_element.split(",")[:3]).upper() - time = time_element.split(",")[-1].strip() - if date: - expiration = convert_date(date) - - image_path = listing.find("img").attrs.get("src") - image_alt = listing.find("img").attrs.get("alt") - - url = listing.find("a").attrs.get("href") - - category_style = "tech-talk" - category_name = "Tech Talk" - if description: - if "coding club" in description.lower(): - category_style = "coding-club" - category_name = "Coding Club" - elif "writing club" in description.lower(): - category_style = "writing-club" - category_name = "Writing Club" - elif "book club" in title.lower(): - category_style = "book-club" - category_name = "Book Club" - elif "career club" in title.lower(): - category_style = "career-club" - category_name = "Career Club" - elif "career talk" in description.lower(): - category_style = "career-talk" - category_name = "Career Talk" - - # No longer needed: image path can refer directly to meetup img url - # image_path = download_image(image_path, description, category_style, expiration) - - upcoming_meetups.append( - MeetupEvents(title=title, description=description.replace("\n", " "), - category_style=category_style, category_name=category_name, - date=date, host=host, speaker=speaker, - time=time, expiration=expiration, image=Image(path=image_path, alt=image_alt), - link=WebLink(path=url))) - return upcoming_meetups - - -def process_meetup_data(meetup: dict) -> dict: - """ - Process the meetup data to ensure that it is in the correct format for writing to a YAML file. - :param meetup: The meetup data to process. - :return: The processed meetup data. - """ - meetup["title"] = to_literal_str(meetup["title"]) - meetup["description"] = to_literal_str(meetup["description"]) - meetup["expiration"] = QuotedString(meetup["expiration"]) - if "speaker" in meetup: - meetup["speaker"] = QuotedString(meetup["speaker"]) - if "host" in meetup: - meetup["host"] = QuotedString(meetup["host"]) - if "image" in meetup: - if "path" in meetup["image"]: - meetup["image"]["path"] = to_quoted_str(meetup["image"]["path"]) - if "alt" in meetup["image"]: - meetup["image"]["alt"] = to_quoted_str(meetup["image"]["alt"]) - if "link" in meetup and "title" in meetup["link"]: - meetup["link"]["title"] = to_quoted_str(meetup["link"]["title"]) - return meetup - - -def write_yaml_file(file_path, data) -> None: - """ - Writes the given data to a YAML file. - - :param file_path: The path of the YAML file to write to. - :param data: The data to write to the file. - :return: None - """ - try: - with open(file_path, "w") as file: - for yaml_obj in data: - file.write(yaml.dump([yaml_obj], sort_keys=False, width=2000)) - file.write("\n") - except (IOError, yaml.YAMLError) as e: - logging.error(f"Error writing to file '{file_path}': {e}") - raise - - -def export_to_yaml(upcoming_meetups, yaml_file: str, mode: WriteMode): - """ - Appends data to a YAML file. - - :param mode: Write mode to use. - :param upcoming_meetups: Upcoming meetups to append to the YAML file. - :param yaml_file: Path to the YAML file. - :return: None - """ - # Convert Pydantic objects to dictionaries - meetup_dicts = [meetup.dict() for meetup in upcoming_meetups] - existing_data = [] - try: - if mode == WriteMode.APPEND: - logging.info("Appending to existing YML file") - with open(yaml_file, "r") as file: - existing_data = yaml.safe_load(file) or [] # Handle empty file - existing_data.extend(meetup_dicts) - meetup_dicts = existing_data - if not isinstance(existing_data, list): - raise ValueError("Existing data is not a list") - - meetup_dicts = [process_meetup_data(meetup) for meetup in meetup_dicts] - write_yaml_file(yaml_file, meetup_dicts) - - except FileNotFoundError: - print(f"File '{yaml_file}' not found. Creating a new file.") - with open(yaml_file, "w") as file: - yaml.dump(meetup_dicts, file, default_flow_style=False, sort_keys=False) - - except (IOError, yaml.YAMLError) as e: - logging.error(f"Error processing file '{yaml_file}': {e}") - raise - - logging.info("Data exported to %s", yaml_file) - - -def extract_name(user_info: Tag) -> str: - """ - Extracts the name from the user info element. - - :param user_info: The user info element. - :return: The name of the user. - """ - u_list = user_info.find_all("strong") - if len(u_list) > 1: - user_info = u_list[1].text.strip() - else: - user_info = u_list[0].text.strip() - user_info = user_info.split(":")[1] - return user_info.strip() - - -def convert_date(date_str: str) -> str: - """ - Convert the date string to the desired format. - :param date_str: The date string to convert. - :return: The date string in the desired format. - """ - # Parse the date string to a datetime object - date_obj = datetime.strptime(date_str, "%a, %b %d, %Y") - # Format the datetime object to the desired string format - return date_obj.strftime("%Y%m%d") - - -def fetch_events(): - logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') - - if len(sys.argv) == 4: - meetup_group_url = sys.argv[1] - yml_file_path = sys.argv[2] - mode = WriteMode(sys.argv[3]) - else: - meetup_group_url = "https://www.meetup.com/women-coding-community/events/?type=upcoming" - yml_file_path = "../_data/events.yml" - mode = WriteMode.APPEND - - logging.info("Params: Url: %s yml: %s mode: %s", meetup_group_url, yml_file_path, mode) - - upcoming_meetups = get_upcoming_meetups(meetup_group_url) - # expired_meetups = get_expired_meetups("https://www.meetup.com/women-coding-community/events/?type=past") - - # Print the extracted information - logging.info("Upcoming Meetups:") - for meetup in upcoming_meetups: - logging.info(f"{meetup.title}") - export_to_yaml(upcoming_meetups, yml_file_path, mode) - - -if __name__ == "__main__": - fetch_events() +import logging +from enum import Enum +from typing import Optional, Union + +import re +import requests +import yaml +import unicodedata +from bs4 import BeautifulSoup, Tag +from pydantic import BaseModel +from ics import Calendar + +# Banner paths +CODING_CLUB_BANNER = "/assets/images/events/event-coding-club-3.jpg" +WRITING_CLUB_BANNER = "/assets/images/events/event-writing-club.jpeg" + +# ----- YAML formatting classes ----- +class LiteralString(str): pass +class QuotedString(str): pass +class NoQuoteString(str): pass + +def literal_string_representer(dumper, data): + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') + +def double_quote_representer(dumper, data): + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"') + +def no_quote(dumper, data): + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='') + +yaml.add_representer(LiteralString, literal_string_representer) +yaml.add_representer(QuotedString, double_quote_representer) +yaml.add_representer(NoQuoteString, no_quote) + + +def to_literal_str(data: str) -> Union[LiteralString, str]: + special_characters = "!@#$%^&*()-+?_=,<>/:;\\" + + if data and ("\n" in data or any(c in special_characters for c in data)): + return LiteralString(data.rstrip() + "\n") + return data + +def to_quoted_str(data: str) -> Union[QuotedString, NoQuoteString]: + special_characters = "!@#$%^&*()-+?_=,<>/:;\\" + + if data and ("\n" in data or any(c in special_characters for c in data)): + return QuotedString(data) + return NoQuoteString(data) + + +# ----- Models ------ +class Image(BaseModel): + path: str = "/assets/images/events/default.jpg" + alt: str = "Square poster of event" + +class WebLink(BaseModel): + path: Optional[str] + title: str = "View meetup event" + target: str = "_target" + +class MeetupEvents(BaseModel): + title: str + description: str + category_style: Optional[str] = "tech-talk" + category_name: Optional[str] = "Tech Talk" + date: str + expiration: Optional[str] = "" + host: Optional[str] = "" + speaker: Optional[str] = "" + time: Optional[str] = "" + image: Optional[Image] + link: Optional[WebLink] + + +# ----- Helper function to clean bold/italics markdown from a name ----- +def clean_name(s): + s = re.sub(r'[*_~`]+', '', s) + s = s.strip() + s = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', s) + if '|' in s: + s = s.split('|')[0].strip() + return s + +# ----- Gets all hosts/co-hosts/speakers and formats accordingly ------- +def get_hosts_and_speakers(event_desc: str) -> tuple[str, str]: + hosts = [] + cohosts = [] + speakers = [] + + text = event_desc.replace('\\', '') + lines = text.splitlines() + + for line in lines: + line = line.strip() + + host_match = re.match(r'\**Host:\**\s*(.+)', line, re.IGNORECASE) + if host_match: + host_name = clean_name(host_match.group(1)) + if host_name: + hosts.append(host_name) + continue + + cohost_match = re.match(r'\**Co-host:\**\s*(.+)', line, re.IGNORECASE) + if cohost_match: + cohost_name = clean_name(cohost_match.group(1)) + if cohost_name: + cohosts.append(cohost_name) + continue + + speaker_match = re.match(r'\**(Guest Presenter|Speaker):\**\s*(.+)', line, re.IGNORECASE) + if speaker_match: + speaker_name = clean_name(speaker_match.group(2)) + if speaker_name: + speakers.append(speaker_name) + continue + + speaker = ', '.join(speakers) + host = "" + + if hosts and cohosts: + host = f"{', '.join(hosts)} and {', '.join(cohosts)}" + elif cohosts and not hosts: + host = ', '.join(cohosts) + else: + host = ', '.join(hosts) + + return host, speaker + +# ----- Removes all formatting, unicodes, emojis, etc from event description ----- +def clean_description(text: str) -> str: + text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text) + text = re.sub(r'[*_~`]+', '', text) + text = unicodedata.normalize('NFKD', text) + allowed_chars = set( + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + " \t\n\r" + ".,;:!?'\"-()’" + ) + text = ''.join(ch for ch in text if ch in allowed_chars) + return text + +# ----- Truncates event description to 1st sentence only and removes WCC prefix in sentence ----- +def get_formatted_event_description(event_desc: str) -> str: + full_description = (clean_description(event_desc) or "").strip() + first_sentence = full_description.split(".")[0].strip() + if first_sentence: + description = f"{first_sentence}." + else: + description = full_description # use full description if cannot truncate to 1st sentence only + + prefix = "Women Coding Community" + if description.strip().startswith(prefix): + return description.strip()[len(prefix):].lstrip() + + return description.strip() + +# ------ Scrape a single Meetup event page to extract the main image URL ------ +def get_event_image_url(url: str) -> str: + response = requests.get(url) + soup = BeautifulSoup(response.content, "html.parser") + + # Look for the Open Graph image tag first (most reliable) + og_image = soup.find("meta", property="og:image") + if og_image: + return og_image.get("content") + + # Fallback + img_tag = soup.find("img") + if img_tag: + image_url = img_tag.get("src") + + return image_url + + +# --- Main logic using downloaded iCal file --- +def get_upcoming_meetups_from_ical_file(ical_path: str) -> list[MeetupEvents]: + with open(ical_path, "r", encoding="utf-8") as f: + calendar = Calendar(f.read()) + + upcoming_meetups: list[MeetupEvents] = [] + + for event in calendar.events: + title = event.name + date_obj = event.begin.datetime + expiration = date_obj.strftime("%Y%m%d") + date = date_obj.strftime("%a, %b %d, %Y").upper() + time = event.begin.datetime.strftime("%I:%M %p %Z") + url = event.url or "" + + full_description = (event.description or "").strip() + + host, speaker = get_hosts_and_speakers(full_description) + description = get_formatted_event_description(full_description) + image_url = get_event_image_url(url) + + # Categorize event type + category_style = "tech-talk" + category_name = "Tech Talk" + if "coding club" in description.lower(): + category_style = "coding-club" + category_name = "Coding Club" + elif "writing club" in description.lower(): + category_style = "writing-club" + category_name = "Writing Club" + elif "book club" in title.lower(): + category_style = "book-club" + category_name = "Book Club" + elif "career club" in title.lower(): + category_style = "career-club" + category_name = "Career Club" + elif "career talk" in description.lower(): + category_style = "career-talk" + category_name = "Career Talk" + + upcoming_meetups.append( + MeetupEvents( + title=title, + description=description.replace("\n", " "), + category_style=category_style, + category_name=category_name, + date=date, + time=time, + expiration=expiration, + host=host, + speaker=speaker, + image=Image(path=image_url, alt="WCC Meetup event image"), + link=WebLink(path=url), + ) + ) + return upcoming_meetups + +# --- Processing and output --- +def process_meetup_data(meetup: dict) -> dict: + meetup["title"] = to_literal_str(meetup["title"]) + meetup["description"] = to_literal_str(meetup["description"]) + meetup["expiration"] = QuotedString(meetup["expiration"]) + meetup["host"] = QuotedString(meetup.get("host", "")) + meetup["speaker"] = QuotedString(meetup.get("speaker", "")) + if "image" in meetup: + meetup["image"]["path"] = to_quoted_str(meetup["image"]["path"]) + meetup["image"]["alt"] = to_quoted_str(meetup["image"]["alt"]) + if "link" in meetup and "title" in meetup["link"]: + meetup["link"]["title"] = to_quoted_str(meetup["link"]["title"]) + return meetup + +# ---- Write specified data to file ----- +def write_yaml_file(file_path, data) -> None: + try: + with open(file_path, "w") as file: + for yaml_obj in data: + file.write(yaml.dump([yaml_obj], sort_keys=False, width=2000)) + file.write("\n") + except (IOError, yaml.YAMLError) as e: + logging.error(f"Error writing to file '{file_path}': {e}") + raise + +# ---- Process events data into yaml file ----- +def export_to_yaml(upcoming_meetups, yaml_file: str): + meetup_dicts = [meetup.dict() for meetup in upcoming_meetups] + try: + meetup_dicts = [process_meetup_data(meetup) for meetup in meetup_dicts] + write_yaml_file(yaml_file, meetup_dicts) + + except FileNotFoundError: + logging.warning(f"File '{yaml_file}' not found. Creating a new file.") + with open(yaml_file, "w") as file: + yaml.dump(meetup_dicts, file, default_flow_style=False, sort_keys=False) + except (IOError, yaml.YAMLError) as e: + logging.error(f"Error processing file '{yaml_file}': {e}") + raise + + +# --- Script Start --- +def fetch_events(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + ical_file_path = "files/meetup.ics" + yml_file_path = "../_data/imported_events.yml" + + logging.info("Params: iCal URL: %s yml: %s mode: %s", ical_file_path, yml_file_path) + upcoming_meetups = get_upcoming_meetups_from_ical_file(ical_file_path) + + logging.info("Upcoming Meetups:") + for meetup in upcoming_meetups: + logging.info(f"{meetup.title}") + export_to_yaml(upcoming_meetups, yml_file_path) + + +if __name__ == "__main__": + fetch_events() diff --git a/tools/requirements.txt b/tools/requirements.txt index c1bb2669..9d9c4b33 100644 Binary files a/tools/requirements.txt and b/tools/requirements.txt differ diff --git a/tools/run_meetup_import.sh b/tools/run_meetup_import.sh index 8b2321a5..c27751aa 100644 --- a/tools/run_meetup_import.sh +++ b/tools/run_meetup_import.sh @@ -7,8 +7,4 @@ source myenv/bin/activate # Install packages pip install -r requirements.txt -# Enter the parameters: FILE_PATH_MENTORS_XLSX FILE_PATH_MENTORS_YML MODE SKIP_ROWS -# Example: samples/mentors.xlsx samples/mentors.yml a -# mode "a" for APPEND new mentors from the xlsx table to the existing mentors.yml -# mode "w" for WRITE all mentors from the xlsx table to mentors.yml -python3.12 meetup_import.py "https://www.meetup.com/women-coding-community/events/?type=upcoming" "../_data/imported_events.yml" a \ No newline at end of file +python3.12 meetup_import.py \ No newline at end of file