Upcoming Events
% if events:-
% for event in events:
- {{event.title}}
- % end
No upcoming events
% endfrom collections import namedtuple from datetime import datetime import logging import os import urllib.parse from bottle import abort, default_app, response, route, run, template from feedgen.feed import FeedGenerator import icalendar as ical from justhtml import JustHTML from markdown import markdown import requests Group = namedtuple("Group", "name url description events") Event = namedtuple("Event", "url title description start_time end_time venue") Venue = namedtuple("Venue", "name address city state country") class GroupNotFoundException(Exception): pass app = default_app() if os.getenv("MEETFREE_BASE_URL"): app.config["base_url"] = os.getenv("MEETFREE_BASE_URL") else: logging.warning("MEETFREE_BASE_URL environment variable unset; " "Atom feed will not have \"self\" URL.") if os.getenv("MEETFREE_ALLOWED_GROUPS"): app.config["allowed_groups"] = set( os.getenv("MEETFREE_ALLOWED_GROUPS").split()) def graphql_query(endpoint, query, **variables): response = requests.post(endpoint, json={"query": query, "variables": variables}, headers={"Content-Type": "application/json"}) response.raise_for_status() json_response = response.json() if ("errors" not in json_response) or (not "errors" in json_response): return json_response["data"] else: raise Exception(json_response["errors"]) def meetup_group(group_slug): def edge2event(edge): match edge: case { "node": { "eventUrl": url, "title": title, "description": description, "dateTime": start_time, "endTime": end_time, "venue": { "name": venue_name, "address": venue_address, "city": venue_city, "state": venue_state, "country": venue_country } } }: return Event(url, title, description, datetime.fromisoformat(start_time), datetime.fromisoformat(end_time), Venue(venue_name, venue_address, venue_city, venue_state, venue_country)) match graphql_query( "https://www.meetup.com/gql2", """ query ($urlname: String!) { groupByUrlname(urlname: $urlname) { name link description events(status: ACTIVE, first: 5) { edges { node { title description dateTime endTime eventUrl venue { name address city state country } } } } } } """, urlname=group_slug)["groupByUrlname"]: case { "name": name, "link": url, "description": description, "events": { "edges": edges } }: return Group(name, url, description, [edge2event(edge) for edge in edges]) case _: raise GroupNotFoundException(group_slug) def is_blank(string): return not string.strip() def venue2location(venue): return ", ".join( part for part in [venue.name, venue.address, venue.city, venue.state, venue.country] if not is_blank(part)) def human_date(dt): return dt.strftime("%A, %B %-d, %Y at %-I:%M %p") def group2atom(group, feed_self_url): feed = FeedGenerator() feed.id(group.url) feed.title(group.name) feed.link(href=group.url, rel="alternate") if feed_self_url: feed.link(href=feed_self_url, rel="self") for event in group.events: entry = feed.add_entry() entry.id(event.url) entry.title(event.title) entry.description(f""" Where: {venue2location(event.venue)} When: {human_date(event.start_time)} {event.description} """) entry.link(href=event.url) return feed def group2ical(group): return ical.Calendar.new( name=group.name, description=group.description, subcomponents=[ ical.Event.new( summary=event.title, description=event.description, start=event.start_time, end=event.end_time, location=venue2location(event.venue) ) for event in group.events ] ) def group2html(group): return template(""" % from datetime import datetime
No upcoming events
% end