diff --git a/tools/events/meetup-automation/event.py b/tools/events/meetup-automation/event.py index 14b08b8cc..322a9f1f0 100644 --- a/tools/events/meetup-automation/event.py +++ b/tools/events/meetup-automation/event.py @@ -128,11 +128,15 @@ def __init__(self, **kwargs) -> None: self.date_time_str = node["dateTime"] self.event_url_str = node["eventUrl"] - venue = node["venue"] + venues = node["venues"] + + # Get the first venue + venue = venues[0] + self.venue_type = venue["venueType"] # TODO: do we need these lat longs? self.lat = venue["lat"] - self.long = venue["lng"] + self.long = venue["lon"] self.event_location = Location(venue["city"], venue["state"], venue["country"]) def to_event(self, group_url: str) -> Event: diff --git a/tools/events/meetup-automation/generate_events_meetup.py b/tools/events/meetup-automation/generate_events_meetup.py index c6b1f565b..db401202f 100644 --- a/tools/events/meetup-automation/generate_events_meetup.py +++ b/tools/events/meetup-automation/generate_events_meetup.py @@ -9,10 +9,44 @@ logger = logging.getLogger(__name__) +_EVENT_LISTING_QUERY = """\ +query($first: Int = 20, $urlName: String!) { + groupByUrlname(urlname: $urlName) { + events(first: $first, sort: ASC) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + id + group { + name + city + state + country + } + title + dateTime + eventUrl + venues { + lat + lon + city + state + country + venueType + } + } + } + } + } +} +""" class TwirMeetupClient: AUTH_ENDPOINT = "https://secure.meetup.com/oauth2/access" - GQL_ENDPOINT = "https://api.meetup.com/gql" + GQL_ENDPOINT = "https://api.meetup.com/gql-ext" def __init__(self) -> None: self._access_token = None @@ -52,154 +86,22 @@ def _get_access_token(self): return self._access_token def _build_event_listing_gql_query(self, group_url_name: str) -> dict: - return { - "query": """ - query ($urlName: String!, $searchEventInput: ConnectionInput!) { - groupByUrlname(urlname: $urlName) { - upcomingEvents(input: $searchEventInput, sortOrder: ASC) { - pageInfo { - hasNextPage - endCursor - } - edges { - node { - id - group { - name - city - state - country - } - title - dateTime - eventUrl - venue { - city - state - country - venueType - lat - lng - } - } - } - } - } - } - """, - "variables": { - "urlName": group_url_name, - "searchEventInput": { - # TODO: see if we need this limit or not - "first": 20 - } - } - } + return { "query": _EVENT_LISTING_QUERY, "variables": { "urlName": group_url_name } } def _parse_event_listing_gql_response(self, response: dict) -> List[RawGqlEvent]: - edges = response["groupByUrlname"]["upcomingEvents"]["edges"] + edges = response["groupByUrlname"]["events"]["edges"] events = [] # TODO: maybe move this validation somewhere else? for edge_kwargs in edges: - if not edge_kwargs["node"]["venue"]: - logger.error(f"Event response missing venue: {edge_kwargs}") + if not edge_kwargs["node"]["venues"]: + logger.error(f"Event response missing venues: {edge_kwargs}") continue events.append(RawGqlEvent(**edge_kwargs)) return events - def fetch_groups(self, endCursor=""): - """ - Returns the response from the API call, which includes data on groups matching the criteria specified in the GraphQL query. - :type endCursor: An optional string parameter used for pagination, indicating the starting point of the query for fetching subsequent pages of results - :rtype: requests.Response - """ - - # Sets the content type to application/json for the request body. - headers = { - "Authorization": f"Bearer {self._get_access_token()}", - "Content-Type": "application/json", - } - - # GraphQL Query: - # Below is a GraphQL query that requests information about groups such as ID, name, link, URL name, latitude, and longitude. - data = { - "query": """ - query ( - $searchGroupInput: ConnectionInput!, - $searchGroupFilter: SearchConnectionFilter!, - $sortOrder: KeywordSort! - ) { - keywordSearch( - input: $searchGroupInput, - filter: $searchGroupFilter, - sort: $sortOrder - ) { - pageInfo { - hasNextPage - endCursor - } - edges { - node { - result { - ... on Group { - id - name - link - urlname - latitude - longitude - } - } - } - } - } - } - """, - # The query filters results based on the keyword "Rust" and sorts them by relevance - "variables": { - "searchGroupFilter": { - "query": "Rust", - "lat": 0.0, - "lon": 0.0, - "radius": 20000, - "source": "GROUPS" - }, - "searchGroupInput": { - "first": 200, - "after": endCursor - }, - "sortOrder":{ - "sortField": "RELEVANCE" - } - } - } - return requests.post(url=self.GQL_ENDPOINT, headers=headers, json=data) - - def get_rust_groups(self) -> dict: - """ - Returns a dictionary where each key represents the unique ID of a group, and the corresponding value is another dictionary containing details about the group such as name, link, URL name, latitude, and longitude - :rtype: dict - """ - endCursor = None - groups = dict() - while True: - response = self.fetch_groups(endCursor).json() - data = response['data'] - edges = data['keywordSearch']['edges'] - pageInfo = data['keywordSearch']['pageInfo'] - for node in edges: - group = node["node"]["result"] - if not (group["id"] in groups): - groups[group["id"]] = group - if pageInfo['hasNextPage']: - endCursor = pageInfo['endCursor'] - else: - break - return groups - def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]: headers = { "Authorization": f"Bearer {self._get_access_token()}", @@ -209,6 +111,7 @@ def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]: logger.info(f"Fetching events for group {group}") query = self._build_event_listing_gql_query(group.url_name) response = requests.post(url=self.GQL_ENDPOINT, headers=headers, json=query) + response.raise_for_status() data = response.json()["data"] logger.debug(data) @@ -217,4 +120,3 @@ def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]: return [] return self._parse_event_listing_gql_response(data) -