Skip to content

Commit 7a3192b

Browse files
committed
update meetup scripts with minor fixes, allow location overriding, support hybrid events going in two sections, remove stale meetup groups
1 parent 2d01c9f commit 7a3192b

File tree

6 files changed

+191
-149
lines changed

6 files changed

+191
-149
lines changed

tools/events/meetup-automation/country_code_to_continent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@
230230
'TZ': 'Africa',
231231
'UA': 'Europe',
232232
'UG': 'Africa',
233+
'UK': 'Europe', # not an actual country code, but using it here
233234
'US': 'North America',
234235
'UY': 'South America',
235236
'UZ': 'Asia',

tools/events/meetup-automation/event.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import string
33
from dataclasses import dataclass
44
from datetime import datetime
5+
from typing import Optional
6+
7+
from utils import LocationOverride
58

69

710
logger = logging.getLogger(__name__)
@@ -37,6 +40,8 @@ def __post_init__(self):
3740
# looks like in GB meetup considers part of the post code as the "state", which is not a common way to write
3841
# locations in GB (or that's my understanding at least)
3942
self.state = None
43+
# and "UK" is more accurate it seems, so replace "GB"
44+
self.country = "UK"
4045

4146

4247
def fields_present(self) -> int:
@@ -76,6 +81,7 @@ class Event:
7681
virtual: bool
7782
organizer_name: str
7883
organizer_url: str
84+
hybrid: bool
7985

8086
def __post_init__(self):
8187
""" Normalize the event data here """
@@ -91,11 +97,17 @@ def to_dict(self) -> dict:
9197
"url": self.url,
9298
"virtual": self.virtual,
9399
"organizer_name": self.organizer_name,
94-
"organizer_url": self.organizer_url
100+
"organizer_url": self.organizer_url,
101+
"hybrid": self.hybrid
95102
}
96103

97104
def to_markdown_string(self) -> str:
98-
location = f"Virtual ({self.location.to_str()})" if self.virtual else self.location.to_str()
105+
if self.hybrid:
106+
location = f"Hybrid ({self.location.to_str()})"
107+
elif self.virtual:
108+
location = f"Virtual ({self.location.to_str()})"
109+
else:
110+
location = self.location.to_str()
99111

100112
return f'* {self.date.date()} | {location} | [{self.organizer_name}]({self.organizer_url})\n * [**{self.name}**]({self.url})'
101113

@@ -112,8 +124,6 @@ class RawGqlEvent:
112124
event_url_str: str
113125
venue_type: None | str
114126
event_location: Location
115-
lat: float
116-
long: float
117127

118128
def __init__(self, **kwargs) -> None:
119129
logger.debug(f"Constructing RawGqlEvent from: {kwargs}")
@@ -130,14 +140,18 @@ def __init__(self, **kwargs) -> None:
130140

131141
venue = node["venue"]
132142
self.venue_type = venue["venueType"]
133-
# TODO: do we need these lat longs?
134-
self.lat = venue["lat"]
135-
self.long = venue["lng"]
136143
self.event_location = Location(venue["city"], venue["state"], venue["country"])
137144

138-
def to_event(self, group_url: str) -> Event:
145+
def to_event(self, group_url: str, location_override: Optional[LocationOverride]) -> Event:
146+
is_hybrid = False
139147
is_virtual = self.venue_type == "online"
140148

149+
if location_override:
150+
if location_override == LocationOverride.VIRTUAL:
151+
is_virtual = True
152+
elif location_override == LocationOverride.HYBRID:
153+
is_hybrid = True
154+
141155
# this is a bit weird because we want a naive datetime object that just contains the year/month/day because we get
142156
# timestamps with tz info like "2025-01-16T19:00+01:00", just strip the time and tz info before parsing
143157
date = datetime.strptime(self.date_time_str.split('T')[0], '%Y-%m-%d')
@@ -155,6 +169,7 @@ def to_event(self, group_url: str) -> Event:
155169
url=self.event_url_str,
156170
virtual=is_virtual,
157171
organizer_name=self.group_name,
158-
organizer_url=group_url
172+
organizer_url=group_url,
173+
hybrid=is_hybrid
159174
)
160175

tools/events/meetup-automation/generate_events_meetup.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from jwt_auth import generate_signed_jwt
66
from event import Event, RawGqlEvent
7-
from utils import MeetupGroupUrl
8-
from typing import List
7+
from utils import LocationOverride, MeetupGroupUrl
8+
from typing import List, Optional
99

1010
logger = logging.getLogger(__name__)
1111

@@ -96,15 +96,27 @@ def _build_event_listing_gql_query(self, group_url_name: str) -> dict:
9696
}
9797
}
9898

99-
def _parse_event_listing_gql_response(self, response: dict) -> List[RawGqlEvent]:
99+
def _parse_event_listing_gql_response(self, response: dict, location_override: Optional[LocationOverride]) -> List[RawGqlEvent]:
100100
edges = response["groupByUrlname"]["upcomingEvents"]["edges"]
101101

102102
events = []
103103
# TODO: maybe move this validation somewhere else?
104104
for edge_kwargs in edges:
105105
if not edge_kwargs["node"]["venue"]:
106-
logger.error(f"Event response missing venue: {edge_kwargs}")
107-
continue
106+
# for events where there's no venue in the response and we have a virtual override, inherit the location from the group here
107+
# a bit gross but oh well
108+
if location_override == LocationOverride.VIRTUAL:
109+
logger.info(f"Overriding location for event: {edge_kwargs}")
110+
venue = {}
111+
venue["city"] = edge_kwargs["node"]["group"]["city"]
112+
venue["state"] = edge_kwargs["node"]["group"]["state"]
113+
venue["country"] = edge_kwargs["node"]["group"]["country"]
114+
venue["venueType"] = "online"
115+
116+
edge_kwargs["node"]["venue"] = venue
117+
else:
118+
logger.error(f"Event response missing venue: {edge_kwargs}")
119+
continue
108120

109121
events.append(RawGqlEvent(**edge_kwargs))
110122

@@ -206,7 +218,7 @@ def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]:
206218
"Content-Type": "application/json",
207219
}
208220

209-
logger.info(f"Fetching events for group {group}")
221+
logger.debug(f"Fetching events for group {group}")
210222
query = self._build_event_listing_gql_query(group.url_name)
211223
response = requests.post(url=self.GQL_ENDPOINT, headers=headers, json=query)
212224
data = response.json()["data"]
@@ -216,5 +228,5 @@ def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]:
216228
logger.error(f"Group {group} not valid, skipping")
217229
return []
218230

219-
return self._parse_event_listing_gql_response(data)
231+
return self._parse_event_listing_gql_response(data, group.location_override)
220232

tools/events/meetup-automation/main.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def main():
3535
for group_url in group_urls:
3636
group_raw_events = meetup_client.get_raw_events_for_group(group_url)
3737

38-
events += [raw_event.to_event(group_url.url) for raw_event in group_raw_events]
38+
events += [raw_event.to_event(group_url.url, group_url.location_override) for raw_event in group_raw_events]
3939

4040
# Remove events outside of date range.
4141
events = date_window_filter(events, args.weeks)
@@ -74,7 +74,7 @@ def output_to_screen(event_list):
7474
if len(value) == 0:
7575
continue
7676
else:
77-
print(f'### {key}:\n')
77+
print(f'### {key}')
7878

7979
# Output event details
8080
for event in value:
@@ -104,9 +104,13 @@ def group_virtual_continent(event_list):
104104
separated_event_list = {}
105105

106106
for event in event_list:
107-
# Separates Events by Virtual or by Continent
108-
key = "Virtual" if event.virtual else country_code_to_continent(event.location.country)
109-
separated_event_list.setdefault(key, []).append(event)
107+
if event.hybrid:
108+
keys = "Virtual", country_code_to_continent(event.location.country)
109+
for key in keys:
110+
separated_event_list.setdefault(key, []).append(event)
111+
else:
112+
key = "Virtual" if event.virtual else country_code_to_continent(event.location.country)
113+
separated_event_list.setdefault(key, []).append(event)
110114

111115
return separated_event_list
112116

tools/events/meetup-automation/utils.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
from dataclasses import dataclass
2-
from typing import List
2+
from enum import Enum
3+
from typing import List, Optional
34
from urllib.parse import urlparse
45
import json
56

7+
class LocationOverride(str, Enum):
8+
HYBRID = "hybrid"
9+
VIRTUAL = "virtual"
10+
11+
612
@dataclass
713
class MeetupGroupUrl:
814
MEETUP_HOSTNAME = "www.meetup.com"
915

1016
url: str
1117
url_name: str
18+
location_override: Optional[LocationOverride]
1219

13-
def __init__(self, url_str: str) -> None:
20+
def __init__(self, url_str: str, location_override: Optional[str]) -> None:
1421
parsed = urlparse(url_str)
1522

1623
if parsed.hostname != self.MEETUP_HOSTNAME:
@@ -23,11 +30,21 @@ def __init__(self, url_str: str) -> None:
2330

2431
self.url = url_str
2532
self.url_name = path_split[1]
26-
33+
34+
if location_override:
35+
self.location_override = LocationOverride(location_override)
36+
else:
37+
self.location_override = None
38+
2739

2840
def read_meetup_group_urls(meetups_json: str) -> List[MeetupGroupUrl]:
2941
with open(meetups_json, "r") as f:
3042
group_urls = json.loads(f.read())
43+
parsed_groups = []
44+
45+
for url, metadata in group_urls.items():
46+
location_override = metadata.get("location_override")
47+
parsed = MeetupGroupUrl(url, location_override)
48+
parsed_groups.append(parsed)
3149

32-
parsed_groups = [MeetupGroupUrl(url) for url in group_urls]
3350
return parsed_groups

0 commit comments

Comments
 (0)