diff --git a/Makefile b/Makefile index 741b761b..1098fafe 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,9 @@ out/townscript.txt: out/bluetokai.json: python src/bluetokai.py +out/gullytours.json: + python src/gullytours.py + all: out/allevents.txt \ out/highape.txt \ out/mapindia.json \ @@ -91,6 +94,7 @@ all: out/allevents.txt \ out/sumukha.json \ out/sofar.json \ out/bluetokai.json \ + out/gullytours.json \ out/townscript.txt @echo "Done" diff --git a/in/known-hosts.yml b/in/known-hosts.yml index 172bb87c..97823551 100644 --- a/in/known-hosts.yml +++ b/in/known-hosts.yml @@ -4,6 +4,52 @@ luma: blumeventures: blumeventures # TWIF has events all across globe # thisweekinfintech: twif +gullytours: + - url: https://www.gully.tours/tours/coffee-and-the-cantonment + id: "88252" + name: "Coffee and the Cantonment" + - url: https://www.gully.tours/tours/colonial-crawl + id: "36715" + name: "Colonial Crawl" + - url: https://www.gully.tours/tours/death-by-dosa + id: "13044" + name: "Death by Dosa" + - url: https://www.gully.tours/tours/death-by-dosa-womens-only + id: "137262" + name: "Death by Dosa (Women Only)" + - url: https://www.gully.tours/tours/malleshwaram-hogona + id: "40636" + name: "Malleshwaram Hogona" + - url: https://www.gully.tours/tours/pete-by-night + id: "9094" + name: Pete by Night + - url: https://www.gully.tours/tours/pete-by-night-womens-only + id: "36716" + name: Pete by Night (Women Only) + - url: https://www.gully.tours/tours/the-soba-walk + id: "22253" + name: The Soba Walk + - url: https://www.gully.tours/tours/the-tiffin-trail + id: "20524" + name: The Tiffin Trail + - url: https://www.gully.tours/tours/the-urban-nature-trail + id: "43157" + name: The Urban Nature Trail + - url: https://www.gully.tours/tours/the-yelahanka-diaries + id: "77823" + name: The Yelahanka Diaries + + - url: https://www.gully.tours/tours/yulu-midnight-trail + id: "12067" + name: Yulu Midnight Trail + - url: https://www.gully.tours/tours/cantt-by-night + id: "43904" + name: Cantt by Night + + - url: https://www.gully.tours/tours/cantt-by-night-womens-only + id: "43161" + name: Cantt by Night (Women Only) + townscript: lahe-lahe: id: "2033748" @@ -11,7 +57,6 @@ townscript: id: "4527655" broke-bibliophiles: id: "1306109" - superEvent: https://www.townscript.com/o/abhiram-r-401313 instagram: - amiel_gourmet - uskbengaluru diff --git a/requirements.txt b/requirements.txt index 2916617b..a896aed3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ datefinder==0.7.3 extruct==0.16.0 html-text==0.5.2 html2md==0.1.7 +html2text==2024.2.26 html5lib==1.1 ics==0.7.2 idna==3.6 diff --git a/src/event-fetcher.py b/src/event-fetcher.py index 6d92c4c8..8a83edd6 100644 --- a/src/event-fetcher.py +++ b/src/event-fetcher.py @@ -12,6 +12,7 @@ 'out/sumukha.json', 'out/bluetokai.json', 'out/champaca.json', + 'out/gullytours.json', ] KNOWN_EVENT_TYPES = [ diff --git a/src/gullytours.py b/src/gullytours.py new file mode 100644 index 00000000..3b2ff915 --- /dev/null +++ b/src/gullytours.py @@ -0,0 +1,94 @@ +import yaml +import json +from datetime import timedelta, datetime, timezone +from requests_cache import CachedSession +from bs4 import BeautifulSoup +from math import ceil + +def get_description(session, url): + soup = BeautifulSoup(session.get(url).content, "html.parser") + desc = "" + for e in soup.select('.rlr-readmore-desc__content'): + if "Lorem" not in e.text and "Dolor" not in e.text: + desc += e.text + + return desc + +def get_calendar(session, tour_id): + start_date = datetime.now().strftime('%Y-%m-%d'), + end_date = (datetime.now() + timedelta(days=30)).strftime('%Y-%m-%d') + url = f"https://gullytours.vacationlabs.com/itineraries/trips/{tour_id}/departure_calendar.json" + + querystring = { + "start": start_date, + "end": end_date, + } + d = session.get(url, params=querystring).json() + keys = d['departure_calendar']['availability_keys'] + c = d['departure_calendar']['availability']['data'] + for date in c: + for x in c[date].values(): + for trip in x: + trip_details = dict(zip(keys, trip)) + price = d['departure_calendar']['pricings'][str(trip_details['pricing_group_id'])]['sticker_price'] + trip_details['price'] = str(ceil(price)) + del trip_details['pricing_group_id'] + yield trip_details + + +def main(): + session = CachedSession( + "event-fetcher-cache", + expire_after=timedelta(days=1), + stale_if_error=True, + use_cache_dir=True, + cache_control=False, + ) + + events = [] + for tour in read_config(): + description = get_description(session, tour['url']) + for trip in get_calendar(session, tour['id']): + events.append(make_event(tour, description, trip)) + + with open("out/gullytours.json", "w") as f: + json.dump(events, f, indent=2) + +# generates a valid schema.org/SocialEvent object +def make_event(tour, description, trip): + tz = timezone(timedelta(hours=5, minutes=30)) + start_time = datetime.fromisoformat(trip['starts_at']).replace(tzinfo=tz) + end_time = datetime.fromisoformat(trip['ends_at']).replace(tzinfo=tz) + event = { + "@context": "http://schema.org", + "url": tour['url'], + "@type": "SocialEvent", + "name": tour.get('name'), + "description": description, + "startDate": start_time.isoformat(), + "endDate": end_time.isoformat(), + "eventAttendanceMode": "OfflineEventAttendanceMode", + "maximumAttendeeCapacity": trip['total_capacity'], + "remainingAttendeeCapacity": trip['available_seats'], + # "image": trip['image'], + "offers": [{ + "@type": "Offer", + "price": trip['price'], + "priceCurrency": "INR", + }], + "organizer": {"@type": "Organization", "name": "Gully Tours"}, + # "location": { + # "@type": "Place", + # "name": trip['location'], + # "address": trip['location'] + " Bengaluru", + # } + } + return event + +def read_config(): + return yaml.safe_load(open('in/known-hosts.yml'))['gullytours'] + +if __name__ == "__main__": + + main() +