Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions tools/events/meetup-automation/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
178 changes: 40 additions & 138 deletions tools/events/meetup-automation/generate_events_meetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()}",
Expand All @@ -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)

Expand All @@ -217,4 +120,3 @@ def get_raw_events_for_group(self, group: MeetupGroupUrl) -> List[RawGqlEvent]:
return []

return self._parse_event_listing_gql_response(data)