Spaces:
Sleeping
Sleeping
| from flask import Flask, request, jsonify,render_template | |
| from flask_cors import CORS | |
| import requests | |
| import json | |
| from datetime import datetime , timedelta | |
| import math | |
| import os | |
| import time | |
| import csv | |
| import sqlite3 | |
| from bs4 import BeautifulSoup | |
| ORS_API_KEY=os.getenv('OSI_API_KEY') | |
| Base_url="https://api.openrouteservice.org" | |
| app=Flask(__name__) | |
| APSRTC_HEADERS = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", | |
| "Accept": "*/*", | |
| "Accept-Encoding": "gzip, deflate, br, zstd", | |
| "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", | |
| "Authorization": "Bearer abhibus", | |
| "Content-Type": "application/json", | |
| "Origin": "https://apsrtclivetrack.com", | |
| "Referer": "https://apsrtclivetrack.com/", | |
| "x-api-key": "53693855434468454E714A596D44457A586975414F573833334833596A584D3333735938444A69357131303D"} | |
| try: | |
| with open('place_id.json', 'r') as f: | |
| APSRTC_DATA = json.load(f) | |
| print(f"Loaded {len(APSRTC_DATA)} stops.") | |
| except Exception as e: | |
| print(f"Error loading JSON: {e}") | |
| APSRTC_DATA = [] | |
| try: | |
| with open('placeid_kerela.json', 'r') as f: | |
| KSRTC_DATA = json.load(f) | |
| print(f"Loaded {len(KSRTC_DATA)} KSRTC stops.") | |
| except Exception as e: | |
| print(f"Error loading KSRTC JSON: {e}") | |
| KSRTC_DATA = [] | |
| try: | |
| with open('placeid_tnstc_template.json', 'r') as f: | |
| TNSTC_DATA = json.load(f) | |
| print(f"Loaded {len(TNSTC_DATA)} TNSTC stops.") | |
| except Exception as e: | |
| print(f"Error loading TNSTC JSON: {e}") | |
| TNSTC_DATA = [] | |
| try: | |
| with open('place_id_kr.json', 'r', encoding='utf-8') as f: | |
| raw_kr_data = json.load(f) | |
| KSRTC_KARNATAKA_MAP = {} | |
| if raw_kr_data.get('success') and 'data' in raw_kr_data: | |
| for k, v in raw_kr_data['data'].items(): | |
| city_name = v['Name'].strip().upper() | |
| KSRTC_KARNATAKA_MAP[city_name] = v['ID'] | |
| print(f"Loaded {len(KSRTC_KARNATAKA_MAP)} KSRTC Karnataka stops.") | |
| except Exception as e: | |
| print(f"Error loading KSRTC Karnataka JSON: {e}") | |
| KSRTC_KARNATAKA_MAP = {} | |
| class TNSTCSessionManager: | |
| def __init__(self): | |
| self.session = requests.Session() | |
| self.last_refresh_time = 0 | |
| self.refresh_interval = 15 * 60 | |
| self.headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", | |
| "Accept": "text/html, */*; q=0.01", | |
| "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", | |
| "Origin": "https://www.tnstc.in", | |
| "Referer": "https://www.tnstc.in/OTRSOnline/jqreq.do?hiddenAction=SearchService", | |
| "X-Requested-With": "XMLHttpRequest" | |
| } | |
| self.session.headers.update(self.headers) | |
| def refresh_session(self): | |
| try: | |
| self.session.get("https://www.tnstc.in/home.do", timeout=10) | |
| self.session.get("https://www.tnstc.in/OTRSOnline/jqreq.do?hiddenAction=SearchService", timeout=10) | |
| self.last_refresh_time = datetime.now().timestamp() | |
| except Exception as e: | |
| print(f"Failed to refresh session: {e}") | |
| def get_valid_session(self): | |
| current_time = datetime.now().timestamp() | |
| if (current_time - self.last_refresh_time) > self.refresh_interval: | |
| self.refresh_session() | |
| return self.session | |
| class BusCache: | |
| def __init__(self, db_path='/tmp/bus_cache.db'): | |
| self.db_path = db_path | |
| self.init_db() | |
| def init_db(self): | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS bus_searches ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| state TEXT NOT NULL, | |
| from_place TEXT NOT NULL, | |
| to_place TEXT NOT NULL, | |
| search_date TEXT NOT NULL, | |
| buses_data TEXT NOT NULL, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| hit_count INTEGER DEFAULT 1 | |
| ) | |
| ''') | |
| cursor.execute(''' | |
| CREATE INDEX IF NOT EXISTS idx_search | |
| ON bus_searches(state, from_place, to_place, search_date) | |
| ''') | |
| conn.commit() | |
| conn.close() | |
| print("Bus cache database started") | |
| def get_cached_buses(self, state, from_place, to_place, max_age_hours=2): | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| today = datetime.now().strftime("%Y-%m-%d") | |
| cutoff_time = datetime.now() - timedelta(hours=max_age_hours) | |
| cursor.execute(''' | |
| SELECT buses_data, created_at, hit_count, id | |
| FROM bus_searches | |
| WHERE state = ? | |
| AND from_place = ? | |
| AND to_place = ? | |
| AND search_date = ? | |
| AND created_at > ? | |
| ORDER BY created_at DESC | |
| LIMIT 1 | |
| ''', (state, from_place.upper(), to_place.upper(), today, cutoff_time.isoformat())) | |
| row = cursor.fetchone() | |
| if row: | |
| buses_data, created_at, hit_count, cache_id = row | |
| cursor.execute(''' | |
| UPDATE bus_searches | |
| SET hit_count = hit_count + 1 | |
| WHERE id = ? | |
| ''', (cache_id,)) | |
| conn.commit() | |
| conn.close() | |
| print(f"CACHE HIT: {state} {from_place}→{to_place} (hits: {hit_count + 1}, age: {created_at})") | |
| return { | |
| 'cached': True, | |
| 'data': json.loads(buses_data), | |
| 'cached_at': created_at, | |
| 'hit_count': hit_count + 1 | |
| } | |
| conn.close() | |
| print(f"CACHE MISS: {state} {from_place}→{to_place}") | |
| return None | |
| def save_buses(self, state, from_place, to_place, buses_data): | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| today = datetime.now().strftime("%Y-%m-%d") | |
| cursor.execute(''' | |
| SELECT id FROM bus_searches | |
| WHERE state = ? | |
| AND from_place = ? | |
| AND to_place = ? | |
| AND search_date = ? | |
| ''', (state, from_place.upper(), to_place.upper(), today)) | |
| existing = cursor.fetchone() | |
| if existing: | |
| cursor.execute(''' | |
| UPDATE bus_searches | |
| SET buses_data = ?, | |
| created_at = CURRENT_TIMESTAMP, | |
| hit_count = 1 | |
| WHERE id = ? | |
| ''', (json.dumps(buses_data), existing[0])) | |
| print(f"CACHE UPDATED: {state} {from_place}→{to_place}") | |
| else: | |
| cursor.execute(''' | |
| INSERT INTO bus_searches | |
| (state, from_place, to_place, search_date, buses_data) | |
| VALUES (?, ?, ?, ?, ?) | |
| ''', (state, from_place.upper(), to_place.upper(), today, json.dumps(buses_data))) | |
| print(f"CACHE SAVED: {state} {from_place}→{to_place}") | |
| conn.commit() | |
| conn.close() | |
| def cleanup_old_cache(self, days_old=7): | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cutoff_date = (datetime.now() - timedelta(days=days_old)).strftime("%Y-%m-%d") | |
| cursor.execute(''' | |
| DELETE FROM bus_searches | |
| WHERE search_date < ? | |
| ''', (cutoff_date,)) | |
| deleted = cursor.rowcount | |
| conn.commit() | |
| conn.close() | |
| if deleted > 0: | |
| print(f"Cleaned up {deleted} old cache entries") | |
| return deleted | |
| bus_cache = BusCache() | |
| bus_cache.cleanup_old_cache(days_old=100) | |
| tnstc_manager = TNSTCSessionManager() | |
| TNSTC_PLACE_CODES = {} | |
| TNSTC_JSON_MAP = {} | |
| try: | |
| with open('SETC_tn.csv', 'r', encoding='utf-8') as f: | |
| reader = csv.DictReader(f) | |
| for row in reader: | |
| from_c = row.get('From', '').strip().upper() | |
| to_c = row.get('To', '').strip().upper() | |
| if from_c: TNSTC_PLACE_CODES[from_c] = from_c[:3] | |
| if to_c: TNSTC_PLACE_CODES[to_c] = to_c[:3] | |
| print(f"Loaded {len(TNSTC_PLACE_CODES)} TNSTC CSV codes.") | |
| except Exception as e: | |
| print(f"Error loading SETC_tn.csv: {e}") | |
| try: | |
| with open('placeid_tnstc_template.json', 'r') as f: | |
| tnstc_places = json.load(f) | |
| for place in tnstc_places: | |
| p_name = place.get('value', '').strip().upper() | |
| p_id = place.get('id', '') | |
| if p_name and p_id: | |
| TNSTC_JSON_MAP[p_name] = p_id | |
| print(f"Loaded {len(TNSTC_JSON_MAP)} TNSTC JSON IDs.") | |
| except Exception as e: | |
| print(f"Error loading TNSTC JSON map: {e}") | |
| def sunposition(lat,lon,timestamp): | |
| lat_rad=math.radians(lat) | |
| long_rad=math.radians(lon) | |
| day_of_year=timestamp.timetuple().tm_yday | |
| hour=timestamp.hour+timestamp.minute/60.0 | |
| declination=23.45 *math.sin(math.radians(360 * (day_of_year)/365)) | |
| dec_rad=math.radians(declination) | |
| hour_angle=15*(hour-12) | |
| ha_rad=math.radians(hour_angle) | |
| sun_altitude=(math.sin(lat_rad)*math.sin(dec_rad)+ math.cos(lat_rad)*math.cos(dec_rad)*math.cos(ha_rad)) | |
| altitude = math.degrees(math.asin(sun_altitude)) | |
| cos_azimuth=((math.sin(dec_rad)-math.sin(lat_rad)*sun_altitude)/(math.cos(lat_rad)*math.cos(math.asin(sun_altitude)))) | |
| cos_azimuth = max(-1,min(1,cos_azimuth)) | |
| azimuth=math.degrees(math.acos(cos_azimuth)) | |
| if hour >12: | |
| azimuth=360-azimuth | |
| return azimuth,altitude | |
| def bearing_calculation(lat1,long1,lat2,long2): | |
| lat1,long1,lat2,long2=map(math.radians,[lat1,long1,lat2,long2]) | |
| dlong=long2-long1 | |
| x=math.sin(dlong) * math.cos(lat2) | |
| y=(math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dlong)) | |
| bearing= math.atan2(x,y) | |
| bearing=math.degrees(bearing) | |
| bearing = (bearing + 360) % 360 | |
| return bearing | |
| def shade_finder(coordinates,start_time_str,duration_min): | |
| hour,minute=map(int,start_time_str.split(':')) | |
| start_time=datetime.now().replace(hour=hour,minute=minute,second=0,microsecond=0) | |
| shade_data=[] | |
| total_segments=len(coordinates)-1 | |
| left_side_time=0 | |
| right_side_time=0 | |
| if hour >= 18 or hour <6: | |
| return{ | |
| 'preferred_side': 'N/A', | |
| 'preferred_window': 'Night journey - shade analysis not applicable', | |
| 'left_shade_minutes': 0, | |
| 'right_shade_minutes': 0, | |
| 'shade_percentage': 0, | |
| 'total_duration': duration_min, | |
| 'is_night': True, | |
| 'segments': [] | |
| } | |
| for i in range(total_segments): | |
| lat1,long1=coordinates[i][1],coordinates[i][0] | |
| lat2,long2=coordinates[i+1][1],coordinates[i+1][0] | |
| segment_time=start_time.timestamp() + (duration_min * 60 * i/total_segments) | |
| current_time=datetime.fromtimestamp(segment_time) | |
| bearing= bearing_calculation(lat1,long1,lat2,long2) | |
| sun_azimuth,sun_altitude=sunposition(lat1,long1,current_time) | |
| if sun_altitude <0: | |
| continue | |
| relative_angle=(sun_azimuth - bearing + 360) % 360 | |
| if 90 <= relative_angle <=270: | |
| shade_side="Right" | |
| left_side_time +=(duration_min/total_segments) | |
| else: | |
| shade_side="LEFT" | |
| right_side_time+=(duration_min/total_segments) | |
| shade_data.append({ | |
| 'segment':i, | |
| 'bearing':round(bearing,2), | |
| 'sun_azimuth': round(sun_azimuth,2), | |
| 'sun_altitude':round(sun_altitude,2), | |
| 'shade_side':shade_side, | |
| 'time':current_time.strftime('%H:%M') | |
| }) | |
| if left_side_time > right_side_time: | |
| preferred_side="LEFT" | |
| preferred_window="Windows on the left side" | |
| shade_percentage=round((left_side_time/duration_min)*100,1) | |
| else: | |
| preferred_side="Right" | |
| preferred_window="Windows on the Right" | |
| shade_percentage=round((right_side_time/duration_min)*100,1) | |
| return { | |
| 'preferred_side': preferred_side, | |
| 'preferred_window': preferred_window, | |
| 'left_shade_minutes': round(left_side_time, 1), | |
| 'right_shade_minutes': round(right_side_time, 1), | |
| 'shade_percentage': shade_percentage, | |
| 'total_duration': duration_min, | |
| 'segments': shade_data[:5] | |
| } | |
| def scrape_fare_only(from_id, to_id): | |
| today_str = datetime.now().strftime("%d/%m/%Y") | |
| web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do" | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", | |
| "Referer": "https://www.apsrtconline.in/oprs-web/", | |
| } | |
| params = { | |
| "txtJourneyDate": today_str, | |
| "startPlaceId": from_id, | |
| "endPlaceId": to_id, | |
| "txtLinkJourneyDate": today_str, | |
| "ajaxAction": "fw", | |
| "qryType": "0" | |
| } | |
| try: | |
| response = requests.get(web_url, headers=headers, params=params, timeout=10) | |
| if response.status_code == 200: | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| fare_map = {} | |
| services = soup.find_all('div', class_='rSetForward') | |
| for service in services: | |
| try: | |
| bus_no_tag = service.find('div', class_='srvceNO') | |
| bus_no = bus_no_tag.get_text(strip=True) if bus_no_tag else None | |
| fare_tag = service.find('span', class_='TickRate') | |
| fare = fare_tag.get_text(strip=True) if fare_tag else "N/A" | |
| if bus_no: | |
| fare_map[bus_no] = fare | |
| except Exception as e: | |
| continue | |
| return fare_map | |
| else: | |
| return {} | |
| except Exception as e: | |
| print(f"Fare scraping error: {e}") | |
| return {} | |
| def detect_state_from_label(label): | |
| label_upper = label.upper() | |
| parts = [part.strip() for part in label.split(',')] | |
| if len(parts) >= 2: | |
| state_code = parts[1].upper() | |
| kerala_codes = ['KL', 'KERALA'] | |
| andhra_codes = ['AP', 'ANDHRA PRADESH'] | |
| if state_code in kerala_codes: | |
| return 'KSRTC' | |
| elif state_code in andhra_codes: | |
| return 'APSRTC' | |
| city = parts[0].upper() if parts else label.upper() | |
| kerala_keywords = ['KERALA', 'KANNUR', 'KOCHI', 'THIRUVANANTHAPURAM', | |
| 'KOZHIKODE', 'THRISSUR', 'PALAKKAD', 'MALAPPURAM', | |
| 'KOLLAM', 'ALAPPUZHA', 'KOTTAYAM', 'IDUKKI', | |
| 'ERNAKULAM', 'KASARAGOD', 'WAYANAD', 'PATHANAMTHITTA'] | |
| andhra_keywords = ['ANDHRA PRADESH', 'VIJAYAWADA', 'VISAKHAPATNAM', | |
| 'TIRUPATI', 'GUNTUR', 'NELLORE', 'KAKINADA', | |
| 'RAJAHMUNDRY', 'KADAPA', 'ANANTAPUR', 'KURNOOL', | |
| 'VIZIANAGARAM', 'ELURU', 'ONGOLE', 'NANDYAL', | |
| 'MACHILIPATNAM', 'TENALI', 'CHITTOOR', 'HINDUPUR', | |
| 'PRODDATUR', 'BHIMAVARAM', 'MADANAPALLE', 'GUNTAKAL', | |
| 'DHARMAVARAM', 'GUDIVADA', 'SRIKAKULAM', 'NARASARAOPET', | |
| 'TADIPATRI', 'TADEPALLIGUDEM', 'CHILAKALURIPET'] | |
| tamil_keywords = ['TAMIL NADU', 'CHENNAI', 'COIMBATORE', 'MADURAI', | |
| 'TRICHY', 'TIRUCHIRAPPALLI', 'SALEM', 'TIRUNELVELI', | |
| 'ERODE', 'VELLORE', 'THOOTHUKUDI', 'THANJAVUR', | |
| 'DINDIGUL', 'CUDDALORE', 'KANCHIPURAM', 'TIRUPPUR', | |
| 'KARUR', 'RAJAPALAYAM', 'SIVAKASI', 'NAGERCOIL', | |
| 'KUMBAKONAM', 'PUDUKKOTTAI', 'HOSUR','TN','CHENNAI'] | |
| karnataka_keywords = ['KARNATAKA', 'BENGALURU', 'BANGALORE', 'MANGALORE', 'MYSURU', 'MYSORE', 'HUBLI', 'BELGAUM', 'SHIVAMOGGA', 'HASSAN', 'UDUPI','KR','KA'] | |
| tg_cities = ['HYDERABAD', 'WARANGAL', 'NIZAMABAD', 'KARIMNAGAR', 'KHAMMAM', 'SECUNDERABAD', 'KUKATPALLY', 'DILSUKHNAGAR', 'MIYAPUR', 'GACHIBOWLI','TG','TS'] | |
| label_upper = label.upper() | |
| for city in tg_cities: | |
| if city in label_upper: | |
| return 'TGSRTC' | |
| for ka_city in karnataka_keywords: | |
| if ka_city in city: | |
| return 'KSRTC-KA' | |
| for tcity in tamil_keywords: | |
| if tcity in city: | |
| return 'TNSTC' | |
| for kcity in kerala_keywords: | |
| if kcity in city: | |
| return 'KSRTC' | |
| for acity in andhra_keywords: | |
| if acity in city: | |
| return 'APSRTC' | |
| return 'APSRTC' | |
| def get_route(): | |
| req_data = request.get_json() | |
| if not req_data: | |
| return jsonify({"error": "No data received"}), 400 | |
| start_coords = req_data.get('start', [77.2090, 28.6139]) | |
| end_coords = req_data.get('end', [78.0081, 27.1767]) | |
| start_time_input = req_data.get('startTime') | |
| url = f"{Base_url}/v2/directions/driving-hgv/geojson" | |
| params = { | |
| "coordinates": [start_coords, end_coords] | |
| } | |
| headers = { | |
| "Authorization": ORS_API_KEY, | |
| "Content-Type": 'application/json' | |
| } | |
| response = requests.post(url, json=params, headers=headers) | |
| if response.status_code != 200: | |
| print("API Error:", response.text) | |
| return jsonify({"error": "ERROR HAI BHAI!! ERROS"}), 400 | |
| data = response.json() | |
| try: | |
| r_coordinate = data['features'][0]['geometry']['coordinates'] | |
| summary = data['features'][0]['properties']['summary'] | |
| dist_km=round(summary['distance']/1000,1) | |
| duration_sec=summary['duration'] | |
| duration_minutes = int(duration_sec / 60) | |
| hours=int(duration_sec//3600) | |
| current_time_str = start_time_input if start_time_input else datetime.now().strftime("%H:%M") | |
| shade_info = shade_finder(r_coordinate, current_time_str, duration_minutes) | |
| minutes=int((duration_sec %3600)//60) | |
| duration_str=f"{hours}h {minutes}min" if hours>0 else f"{minutes}min" | |
| leafly = [[coord[1], coord[0]] for coord in r_coordinate] | |
| return jsonify({ | |
| 'path': leafly, | |
| 'start_point': [start_coords[1], start_coords[0]], | |
| 'end_point': [end_coords[1], end_coords[0]], | |
| 'distance': f"{dist_km} km", | |
| 'duration': duration_str, | |
| 'shade_analysis': shade_info | |
| }) | |
| except (KeyError, IndexError) as e: | |
| return jsonify({"error": "Could not parse route data"}), 500 | |
| def api_search(): | |
| query=request.args.get('q','') | |
| if len(query)<3: | |
| return jsonify([]) | |
| url=f'{Base_url}/geocode/search' | |
| param={ | |
| "api_key":ORS_API_KEY, | |
| "text":query, | |
| "size":5, | |
| "boundary.country":"IN" | |
| } | |
| try: | |
| response=requests.get(url,params=param) | |
| if response.status_code ==200: | |
| data=response.json() | |
| suggestions=[] | |
| for feature in data.get('features',[]): | |
| label=feature['properties']['label'] | |
| state=detect_state_from_label(label) | |
| suggestions.append({ | |
| "label":feature['properties']['label'], | |
| "coords":feature['geometry']['coordinates'], | |
| "state":state | |
| }) | |
| return jsonify(suggestions) | |
| except Exception as e: | |
| print(f"Geocode error:{e}") | |
| return jsonify([]) | |
| def get_place_id(): | |
| req_data=request.get_json() | |
| if not req_data or 'address' not in req_data: | |
| return jsonify({"error":"YEH SAHI BHAT NAHI HAI.. I got no place to give ID for"}),400 | |
| search_string = req_data.get('address','').upper() | |
| state=req_data.get('state','APSRTC') | |
| if state == 'KSRTC': | |
| data_source = KSRTC_DATA | |
| elif state == 'TNSTC': | |
| data_source = TNSTC_DATA | |
| else: | |
| data_source = APSRTC_DATA | |
| for depot in data_source: | |
| stop_name = depot.get('value','').upper() | |
| if stop_name and stop_name in search_string: | |
| return jsonify({ | |
| "id":depot['id'], | |
| "code": depot.get('code', ''), | |
| "match":depot['value'] | |
| }) | |
| return jsonify({ "id":None}) | |
| def findbus(): | |
| req_data = request.get_json() | |
| from_id = req_data.get('fromId') | |
| from_name = req_data.get('fromName', 'Unknown') | |
| to_name = req_data.get('toName', 'Unknown') | |
| to_id = req_data.get('toId') | |
| if not from_id or not to_id: | |
| return jsonify({"error": "Missing Start or End IDs"}), 400 | |
| url = "https://utsappapicached01.apsrtconline.in/uts-vts-api/services/all" | |
| try: | |
| payload = { | |
| "sourceLinkId": int(from_id), | |
| "destinationLinkId": int(to_id), | |
| "sourcePlaceId": int(from_id), | |
| "destinationPlaceId": int(to_id), | |
| "userId": "1363789069449", | |
| "versionCode": "2020254", | |
| "apiVersion": 1 | |
| } | |
| except ValueError: | |
| return jsonify({"error": "IDs must be numeric"}), 400 | |
| try: | |
| response = requests.post(url, headers=APSRTC_HEADERS, json=payload) | |
| if response.status_code == 200: | |
| data = response.json() | |
| fare_data=scrape_fare_only(from_id,to_id) | |
| fare_map=fare_data | |
| fare_value=[] | |
| for fare in fare_map.values(): | |
| try: | |
| fare_value.append(int(fare)) | |
| except: | |
| pass | |
| avg_fare = round(sum(fare_value) / len(fare_value)) if fare_value else None | |
| if data.get('data') and isinstance(data['data'],list): | |
| for bus in data['data']: | |
| bus_no=bus.get('oprsNo','').strip() | |
| bus['fare']=fare_map.get(bus_no,'N/A') | |
| try: | |
| dep_time_obj = datetime.strptime(bus['serviceStartTime'], "%H:%M") | |
| bus['serviceStartTime'] = dep_time_obj.strftime("%I:%M %p") | |
| except (ValueError, TypeError): | |
| pass | |
| try: | |
| arr_time_obj = datetime.strptime(bus['serviceEndTime'], "%H:%M") | |
| bus['serviceEndTime'] = arr_time_obj.strftime("%I:%M %p") | |
| except (ValueError, TypeError): | |
| pass | |
| data['averageFare'] = avg_fare | |
| bus_cache.save_buses('APSRTC', from_name, to_name, data) | |
| return jsonify(data) | |
| else: | |
| print(f"APSRTC Error: {response.status_code} - {response.text}") | |
| return jsonify({"error": "External API Failed", "details": response.text}), 500 | |
| except Exception as e: | |
| print(f"Request Error: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| def findbus2(): | |
| req_data = request.get_json() | |
| from_id = req_data.get('fromId') | |
| to_id = req_data.get('toId') | |
| if not from_id or not to_id: | |
| return jsonify({"error": "Missing Start or End IDs"}), 400 | |
| today_str = datetime.now().strftime("%d/%m/%Y") | |
| web_url = "https://www.apsrtconline.in/oprs-web/forward/booking/avail/services.do" | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", | |
| "Referer": "https://www.apsrtconline.in/oprs-web/", | |
| "Content-Type": "application/x-www-form-urlencoded" | |
| } | |
| params = { | |
| "txtJourneyDate": today_str, | |
| "startPlaceId": from_id, | |
| "endPlaceId": to_id, | |
| "txtLinkJourneyDate": today_str, | |
| "ajaxAction": "fw", | |
| "covidBkgEnable": "", | |
| "qryType": "0" | |
| } | |
| try: | |
| response = requests.get(web_url, headers=headers, params=params) | |
| if response.status_code == 200: | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| bus_list = [] | |
| services = soup.find_all('div', class_='result-grid-box') | |
| if not services: | |
| services = soup.select('.srvceNO') | |
| results = soup.find_all('div', class_='search-result-item') | |
| if not results: | |
| results = soup.find_all('div', class_='rSet') | |
| for service in results: | |
| try: | |
| type_tag = service.find('h3') or service.find('div', class_='srvceName') | |
| srv_type = type_tag.get_text(strip=True) if type_tag else "BUS" | |
| no_tag = service.find('div', class_='srvceNO') | |
| oprs_no = no_tag.get_text(strip=True) if no_tag else "N/A" | |
| start_tag = service.find('span', class_='startTime') | |
| end_tag = service.find('span', class_='endTime') | |
| start_time = start_tag.get_text(strip=True) if start_tag else "00:00" | |
| end_time = end_tag.get_text(strip=True) if end_tag else "00:00" | |
| bus_obj = { | |
| "oprsNo": oprs_no, | |
| "serviceType": srv_type, | |
| "serviceStartTime": start_time, | |
| "serviceEndTime": end_time, | |
| "depotName": "APSRTC", | |
| "journeyDate": today_str | |
| } | |
| bus_list.append(bus_obj) | |
| except Exception as p_err: | |
| print(f"Skipped a row due to error: {p_err}") | |
| continue | |
| print(f"Parsed {len(bus_list)} buses from HTML.") | |
| return jsonify(bus_list) | |
| else: | |
| print(f"Web Error: {response.status_code}") | |
| return jsonify({"error": "Web Failed"}), 500 | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| def get_bus_stops(): | |
| req_data = request.get_json() | |
| doc_id = req_data.get('docId') | |
| if not doc_id: | |
| return jsonify({"error": "Missing docId"}), 400 | |
| url = "https://utsappapicached01.apsrtconline.in/uts-vts-api/servicewaypointdetails/bydocid" | |
| payload = {"docId": doc_id} | |
| try: | |
| response = requests.post(url, headers=APSRTC_HEADERS, json=payload) | |
| if response.status_code == 200: | |
| return jsonify(response.json()) | |
| else: | |
| return jsonify({"error": "Failed to fetch stops", "details": response.text}), response.status_code | |
| except Exception as e: | |
| print(f"Stops API Error: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| def find_buses_kerala(): | |
| req_data = request.get_json() | |
| from_name = req_data.get('fromName', '').split(',')[0].strip().upper() | |
| to_name = req_data.get('toName', '').split(',')[0].strip().upper() | |
| if not from_name or not to_name: | |
| return jsonify({"error": "Missing Locations"}), 400 | |
| src_slug = from_name.replace(" ", "-") | |
| dst_slug = to_name.replace(" ", "-") | |
| base_url = f"https://www.kbuses.in/v3/Find/source/{src_slug}/destination/{dst_slug}/type/all/timing/all" | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", | |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", | |
| "Accept-Language": "en-US,en;q=0.5", | |
| "Referer": "https://www.kbuses.in/" | |
| } | |
| all_buses = [] | |
| current_page = 1 | |
| max_pages = 10 | |
| try: | |
| while current_page <= max_pages: | |
| if current_page == 1: | |
| page_url = base_url | |
| else: | |
| page_url = f"{base_url}?page={current_page}" | |
| response = requests.get(page_url, headers=headers, timeout=20) | |
| if response.status_code != 200: | |
| print(f"{current_page} returned status: {response.status_code}") | |
| break | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| bus_containers = soup.find_all('div', class_='indibus') | |
| if len(bus_containers) == 0: | |
| print(f"No more buses found: {current_page}.") | |
| break | |
| print(f"Found {len(bus_containers)} buses: {current_page}") | |
| for container in bus_containers: | |
| try: | |
| bus_name_elem = container.find('span', class_='busname') | |
| if bus_name_elem: | |
| for icon in bus_name_elem.find_all('svg'): | |
| icon.decompose() | |
| bus_name = bus_name_elem.get_text(strip=True) | |
| else: | |
| bus_name = "KSRTC" | |
| bus_type_elem = container.find('div', class_='bustype') | |
| bus_type = bus_type_elem.get_text(strip=True) if bus_type_elem else "Ordinary" | |
| time_elem = container.find('span', class_='large_bold') | |
| start_time = time_elem.get_text(strip=True) if time_elem else "N/A" | |
| small_txts = container.find_all('span', class_='smalltxt') | |
| end_time = None | |
| duration = None | |
| for span in small_txts: | |
| txt = span.get_text(strip=True) | |
| if "@" in txt: | |
| parts = txt.split('@') | |
| if len(parts) > 1: | |
| end_time = parts[1].strip() | |
| elif "hour" in txt.lower() or "minute" in txt.lower(): | |
| duration = txt | |
| route_info = "" | |
| details_elem = container.find('details') | |
| if details_elem: | |
| route_p = details_elem.find('p') | |
| if route_p: | |
| route_info = route_p.get_text(strip=True) | |
| fare = None | |
| bus_info_divs = container.find_all('div', class_='bus-info') | |
| for div in bus_info_divs: | |
| fare_text = div.get_text() | |
| if 'Fare:' in fare_text or '₹' in fare_text: | |
| import re | |
| fare_match = re.search(r'₹\s*(\d+)', fare_text) | |
| if fare_match: | |
| fare = fare_match.group(1) | |
| break | |
| detail_url = None | |
| detail_link = container.find('a', class_='btn-outline-success') | |
| if detail_link and detail_link.has_attr('href'): | |
| href = detail_link.get('href') | |
| if href.startswith('/'): | |
| detail_url = f"https://www.kbuses.in{href}" | |
| elif href.startswith('http'): | |
| detail_url = href | |
| else: | |
| detail_url = f"https://www.kbuses.in/{href}" | |
| bus_obj = { | |
| "oprsNo": bus_name, | |
| "serviceType": bus_type, | |
| "serviceStartTime": start_time, | |
| "serviceEndTime": end_time if end_time else "N/A", | |
| "duration": duration if duration else "N/A", | |
| "fare": fare if fare else "N/A", | |
| "depotName": "KERALA", | |
| "serviceDocId": detail_url, | |
| "route": route_info, | |
| "journeyDate": datetime.now().strftime("%d/%m/%Y"), | |
| "page": current_page | |
| } | |
| all_buses.append(bus_obj) | |
| except Exception as parse_err: | |
| print(f"Error parsing bus: {parse_err}") | |
| import traceback | |
| traceback.print_exc() | |
| continue | |
| pagination = soup.find('nav', {'aria-label': 'Page navigation'}) | |
| if not pagination: | |
| break | |
| next_link = None | |
| for link in pagination.find_all('a'): | |
| if 'Next' in link.get_text(): | |
| next_link = link.get('href') | |
| break | |
| if not next_link: | |
| print(f"No more pages. Stopping: {current_page}.") | |
| break | |
| current_page += 1 | |
| print(f"Total buses scraped: {len(all_buses)} from {current_page} pages") | |
| return jsonify({ | |
| "data": all_buses, | |
| "source": "KBuses", | |
| "totalPages": current_page, | |
| "totalBuses": len(all_buses) | |
| }) | |
| except requests.Timeout: | |
| print("Request timeout") | |
| return jsonify({"error": "Request timeout"}), 504 | |
| except Exception as e: | |
| print(f"Scraper Error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return jsonify({"error": str(e)}), 500 | |
| def get_kerala_bus_stops(): | |
| req_data = request.get_json() | |
| detail_url = req_data.get('detailUrl') | |
| if not detail_url: | |
| return jsonify({"error": "Missing detail URL"}), 400 | |
| if detail_url.startswith('/'): | |
| detail_url = f"https://www.kbuses.in{detail_url}" | |
| headers = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", | |
| "Referer": "https://www.kbuses.in/" | |
| } | |
| try: | |
| response = requests.get(detail_url, headers=headers, timeout=15) | |
| if response.status_code != 200: | |
| return jsonify({"error": f"Failed to fetch bus details: {response.status_code}"}), 500 | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| stops = [] | |
| table = soup.find('table', class_='table-hover') | |
| if table: | |
| rows = table.find_all('tr') | |
| current_stop_name = "" | |
| for row in rows: | |
| header_th = row.find('th', class_='cell1') | |
| if header_th: | |
| current_stop_name = header_th.get_text(strip=True) | |
| continue | |
| cols = row.find_all('td') | |
| if cols and len(cols) >= 2 and current_stop_name: | |
| stop_time = cols[1].get_text(strip=True) | |
| detail_name = cols[0].get_text(strip=True) | |
| stops.append({ | |
| "placeName": current_stop_name, | |
| "detailName": detail_name, | |
| "scheduleArrTime": stop_time, | |
| "seqNo": len(stops) + 1 | |
| }) | |
| current_stop_name = "" | |
| if not stops: | |
| indibus_div = soup.find('div', class_='card indibus smalltxt') | |
| if indibus_div: | |
| via_div = indibus_div.find('div', style="padding: 5px;") | |
| if via_div: | |
| text = via_div.get_text(strip=True) | |
| if "Via" in text: | |
| clean_text = text.replace("Via ➥", "").replace("Via", "") | |
| parts = clean_text.split('⤳') | |
| for i, part in enumerate(parts): | |
| stops.append({ | |
| "placeName": part.strip(), | |
| "scheduleArrTime": "--:--", | |
| "seqNo": i + 1 | |
| }) | |
| print(f"Extracted {len(stops)} stops for K bus") | |
| return jsonify({"data": stops}) | |
| except Exception as e: | |
| print(f"Error fetching Kerala bus stops: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| def find_buses_tnstc(): | |
| req_data = request.get_json() | |
| from_name = req_data.get('fromName', '') | |
| to_name = req_data.get('toName', '') | |
| if not from_name or not to_name: | |
| return jsonify({"error": "Missing location names"}), 400 | |
| from_place = from_name.split(',')[0].strip().upper() | |
| to_place = to_name.split(',')[0].strip().upper() | |
| print(f"\nTNSTC Request: {from_place} -> {to_place}") | |
| from_code = TNSTC_PLACE_CODES.get(from_place, from_place[:3]) | |
| to_code = TNSTC_PLACE_CODES.get(to_place, to_place[:3]) | |
| if from_place not in TNSTC_PLACE_CODES: | |
| for k, v in TNSTC_PLACE_CODES.items(): | |
| if from_place in k: from_code = v; break | |
| if to_place not in TNSTC_PLACE_CODES: | |
| for k, v in TNSTC_PLACE_CODES.items(): | |
| if to_place in k: to_code = v; break | |
| from_id = TNSTC_JSON_MAP.get(from_place) | |
| to_id = TNSTC_JSON_MAP.get(to_place) | |
| if not from_id: | |
| for k, v in TNSTC_JSON_MAP.items(): | |
| if from_place in k: from_id = v; break | |
| if not to_id: | |
| for k, v in TNSTC_JSON_MAP.items(): | |
| if to_place in k: to_id = v; break | |
| PLACE_ID_MAP = { | |
| 'TRICHY': '74', 'TIRUCHIRAPPALLI': '74', 'COIMBATORE': '114', | |
| 'CHENNAI': '1358', 'MADURAI': '190', 'SALEM': '533', | |
| 'KUMBAKONAM': '80', 'THANJAVUR': '190', 'TIRUNELVELI': '190', | |
| 'ERODE': '190', 'KARUR': '190' | |
| } | |
| if not from_id: from_id = PLACE_ID_MAP.get(from_place) | |
| if not to_id: to_id = PLACE_ID_MAP.get(to_place) | |
| if not from_id or not to_id: | |
| return jsonify({"error": f"Place ID not found for {from_place} or {to_place}"}), 400 | |
| today_str = datetime.now().strftime("%d/%m/%Y") | |
| url = "https://www.tnstc.in/OTRSOnline/jqreq.do" | |
| payload = { | |
| "hiddenStartPlaceID": from_id, | |
| "hiddenEndPlaceID": to_id, | |
| "txtStartPlaceCode": from_code, | |
| "txtEndPlaceCode": to_code, | |
| "hiddenStartPlaceName": from_place, | |
| "hiddenEndPlaceName": to_place, | |
| "matchStartPlace": from_place, | |
| "matchEndPlace": to_place, | |
| "hiddenCurrentDate": today_str, | |
| "hiddenOnwardJourneyDate": today_str, | |
| "hiddenAction": "SearchService", | |
| "languageType": "E", | |
| "checkSingleLady": "N", | |
| "txtJourneyDate": "DD/MM/YYYY", | |
| "txtReturnDate": "DD/MM/YYYY", | |
| "hiddenMaxNoOfPassengers": "16", | |
| "selectStartPlace": from_code, | |
| "selectEndPlace": to_code, | |
| } | |
| try: | |
| session = tnstc_manager.get_valid_session() | |
| response = session.post(url, data=payload, timeout=20) | |
| if "Session Expired" in response.text or response.status_code != 200: | |
| print(" Session expired during request. Retrying...") | |
| tnstc_manager.refresh_session() | |
| session = tnstc_manager.get_valid_session() | |
| response = session.post(url, data=payload, timeout=20) | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| bus_items = soup.find_all('div', class_='bus-item') | |
| buses = [] | |
| for item in bus_items: | |
| try: | |
| operator_elem = item.find('span', class_='operator-name') | |
| operator = operator_elem.get_text(strip=True) if operator_elem else "TNSTC" | |
| type_spans = item.find_all('span', class_='text-muted') | |
| bus_type = type_spans[0].get_text(strip=True) if type_spans else "Unknown" | |
| trip_link = item.find('a', href=True) | |
| trip_code = trip_link.get_text(strip=True) if trip_link else "N/A" | |
| time_divs = item.find_all('div', class_='time-info') | |
| start_time = "N/A" | |
| end_time = "N/A" | |
| if len(time_divs) >= 2: | |
| start_span = time_divs[0].find('span', class_='text-dark') | |
| start_time_raw = start_span.get_text(strip=True) if start_span else "N/A" | |
| end_span = time_divs[-1].find('span', class_='text-dark') | |
| end_time_raw = end_span.get_text(strip=True) if end_span else "N/A" | |
| try: | |
| start_time = datetime.strptime(start_time_raw, "%H:%M").strftime("%I:%M %p") | |
| except (ValueError, TypeError): | |
| start_time = start_time_raw | |
| try: | |
| end_time = datetime.strptime(end_time_raw, "%H:%M").strftime("%I:%M %p") | |
| except (ValueError, TypeError): | |
| end_time = end_time_raw | |
| duration_elem = item.find('span', class_='duration') | |
| duration = duration_elem.get_text(strip=True) if duration_elem else "N/A" | |
| via_elem = item.find('small', style=lambda x: x and 'blue' in x) | |
| via = via_elem.get_text(strip=True).replace('Via-', '') if via_elem else "" | |
| price_elem = item.find('div', class_='price') | |
| fare = price_elem.get_text(strip=True).replace('Rs', '').strip() if price_elem else "N/A" | |
| seats_elem = item.find('span', class_='text-1') | |
| seats = seats_elem.get_text(strip=True).replace('Seats Available', '').strip() if seats_elem else "N/A" | |
| buses.append({ | |
| "oprsNo": trip_code, | |
| "serviceType": bus_type, | |
| "serviceStartTime": start_time, | |
| "serviceEndTime": end_time, | |
| "depotName": operator, | |
| "duration": duration, | |
| "via": via, | |
| "fare": fare, | |
| "availableSeats": seats, | |
| "journeyDate": today_str, | |
| "source": "TNSTC" | |
| }) | |
| except Exception: | |
| continue | |
| print(f"Found {len(buses)} buses") | |
| result={ | |
| "data":buses, | |
| "source":"TNSTC", | |
| "totalBuses":"len(buses)" | |
| } | |
| bus_cache.save_buses('TNSTC', from_place, to_place, result) | |
| return jsonify({ | |
| "data": buses, | |
| "source": "TNSTC", | |
| "totalBuses": len(buses) | |
| }) | |
| except Exception as e: | |
| print(f"Connection Error: {e}") | |
| return jsonify({"error": "TNSTC Connection Failed", "details": str(e)}), 500 | |
| def find_buses_ksrtc_karnataka(): | |
| req_data = request.get_json() | |
| from_name_raw = req_data.get('fromName', '').split(',')[0].strip() | |
| to_name_raw = req_data.get('toName', '').split(',')[0].strip() | |
| journey_date_iso = req_data.get('journeyDate') | |
| if not journey_date_iso: | |
| journey_date_iso = datetime.now().strftime("%Y-%m-%d") | |
| from_id = (KSRTC_KARNATAKA_MAP.get(from_name_raw.lower()) or | |
| KSRTC_KARNATAKA_MAP.get(from_name_raw.upper()) or | |
| KSRTC_KARNATAKA_MAP.get(from_name_raw.title())) | |
| to_id = (KSRTC_KARNATAKA_MAP.get(to_name_raw.lower()) or | |
| KSRTC_KARNATAKA_MAP.get(to_name_raw.upper()) or | |
| KSRTC_KARNATAKA_MAP.get(to_name_raw.title())) | |
| if not from_id or not to_id: | |
| print(f"City ID not found: {from_name_raw} or {to_name_raw}") | |
| return jsonify({"error": "City not found in database"}), 400 | |
| from_name = from_name_raw.title() | |
| to_name = to_name_raw.title() | |
| try: | |
| date_obj = datetime.strptime(journey_date_iso, "%Y-%m-%d") | |
| indian_date = date_obj.strftime("%d-%m-%Y") | |
| except: | |
| indian_date = journey_date_iso | |
| session = requests.Session() | |
| base_headers = { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0', | |
| 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', | |
| 'Accept-Language': 'en-US,en;q=0.5', | |
| 'Accept-Encoding': 'gzip, deflate, br', | |
| 'DNT': '1', | |
| 'Connection': 'keep-alive', | |
| 'Upgrade-Insecure-Requests': '1', | |
| } | |
| try: | |
| session.get('https://ksrtc.in/oprs-web/', headers=base_headers, timeout=10) | |
| time.sleep(0.2) | |
| search_url = f"https://ksrtc.in/search?mode=oneway&fromCity={from_id}|{from_name}&toCity={to_id}|{to_name}&departDate={indian_date}&stationInFromCity=&stationInToCity=&IsSingleLady=0" | |
| search_headers = base_headers.copy() | |
| search_headers['Referer'] = 'https://ksrtc.in/oprs-web/' | |
| session.get(search_url, headers=search_headers, timeout=10) | |
| time.sleep(0.3) | |
| api_headers = { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0', | |
| 'Accept': '*/*', | |
| 'Accept-Language': 'en-US,en;q=0.5', | |
| 'Referer': search_url, | |
| 'DNT': '1', | |
| 'Connection': 'keep-alive', | |
| 'Sec-Fetch-Dest': 'empty', | |
| 'Sec-Fetch-Mode': 'cors', | |
| 'Sec-Fetch-Site': 'same-origin', | |
| } | |
| session.get('https://ksrtc.in/api/resource/getStaticCityList', | |
| headers=api_headers, timeout=10) | |
| api_url = "https://ksrtc.in/api/resource/searchRoutesV4" | |
| to_name_api = to_name | |
| if to_name.lower() == "bengaluru": | |
| to_name_api = "Bangalore" | |
| params = { | |
| 'fromCityID': str(from_id), | |
| 'toCityID': str(to_id), | |
| 'fromCityName': from_name, | |
| 'toCityName': to_name_api, | |
| 'journeyDate': journey_date_iso, | |
| 'mode': 'oneway' | |
| } | |
| response = session.get(api_url, params=params, headers=api_headers, timeout=15) | |
| if response.status_code != 200: | |
| return jsonify({"error": f"API returned {response.status_code}"}), 500 | |
| if len(response.content) < 10: | |
| return jsonify({"error": "Empty response", "data": [], "totalBuses": 0}) | |
| try: | |
| raw_data = response.json() | |
| except json.JSONDecodeError as e: | |
| print(f"JSON Error: {e}") | |
| print(f"Response preview: {response.text[:300]}") | |
| return jsonify({"error": "Invalid JSON"}), 500 | |
| if not isinstance(raw_data, list): | |
| return jsonify({ | |
| "error": "No buses found", | |
| "data": [], | |
| "totalBuses": 0, | |
| "source": "KSRTC-KA" | |
| }) | |
| formatted_buses = [] | |
| for bus in raw_data: | |
| try: | |
| dep_str = bus.get('DepartureTime', '') | |
| arr_str = bus.get('ArrivalTime', '') | |
| start_time = "N/A" | |
| end_time = "N/A" | |
| duration_str = "N/A" | |
| arrival_date = "" | |
| if dep_str and arr_str: | |
| dep_dt = datetime.strptime(dep_str, "%Y-%m-%dT%H:%M:%S") | |
| arr_dt = datetime.strptime(arr_str, "%Y-%m-%dT%H:%M:%S") | |
| duration = arr_dt - dep_dt | |
| hours = duration.seconds // 3600 | |
| minutes = (duration.seconds % 3600) // 60 | |
| duration_str = f"{hours}h {minutes}m" | |
| start_time = dep_dt.strftime("%I:%M %p") | |
| end_time = arr_dt.strftime("%I:%M %p") | |
| arrival_date = arr_dt.strftime("%d %b") | |
| bus_obj = { | |
| "oprsNo": bus.get('TripCode', 'N/A'), | |
| "serviceType": bus.get('ServiceType', 'KSRTC'), | |
| "serviceStartTime": start_time, | |
| "serviceEndTime": end_time, | |
| "arrivalDate": arrival_date, | |
| "duration": duration_str, | |
| "fare": str(bus.get('Fare', 0)), | |
| "availableSeats": str(bus.get('AvailableSeats', 0)), | |
| "depotName": bus.get('CompanyName', 'KSRTC Karnataka'), | |
| "via": bus.get('ViaPlaces', ''), | |
| "amenities": bus.get('AmenitiesType', ''), | |
| "journeyDate": journey_date_iso, | |
| "source": "KSRTC-KA", | |
| "arrangement": bus.get('Arrangement', ''), | |
| "hasAC": bool(bus.get('HasAC', 0)), | |
| "hasSleeper": bool(bus.get('HasSleeper', 0)), | |
| "routeName": bus.get('RouteName', ''), | |
| "serviceId": bus.get('ServiceID', ''), | |
| "tripId": bus.get('TripID', ''), | |
| "serviceDocId": bus.get('RouteScheduleId') | |
| } | |
| formatted_buses.append(bus_obj) | |
| except Exception as parse_err: | |
| print(f" Parse error: {parse_err}") | |
| continue | |
| return jsonify({ | |
| "success": True, | |
| "data": formatted_buses, | |
| "source": "KSRTC-KA", | |
| "totalBuses": len(formatted_buses), | |
| "route": f"{from_name} to {to_name}", | |
| "date": journey_date_iso | |
| }) | |
| except requests.exceptions.Timeout: | |
| return jsonify({"error": "Request timeout"}), 504 | |
| except requests.exceptions.ConnectionError: | |
| return jsonify({"error": "Connection failed"}), 503 | |
| except Exception as e: | |
| print(f" Error: {type(e).__name__}: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| return jsonify({"error": str(e)}), 500 | |
| def get_ksrtc_ka_stops(): | |
| req_data = request.get_json() | |
| route_code = req_data.get('routeCode') | |
| if not route_code: | |
| return jsonify({"error": "Missing Route Code"}), 400 | |
| print(f"Fetching KSRTC-KA Stops for: {route_code}") | |
| session = requests.Session() | |
| headers = { | |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', | |
| 'Referer': 'https://ksrtc.in/oprs-web/', | |
| 'Origin': 'https://ksrtc.in' | |
| } | |
| session.headers.update(headers) | |
| try: | |
| session.get('https://ksrtc.in/oprs-web/', timeout=5) | |
| url = f"https://ksrtc.in/api/resource/ActiveMiddleCities" | |
| params = {"RouteCode": route_code} | |
| response = session.get(url, params=params, timeout=10) | |
| if response.status_code == 200: | |
| data = response.json() | |
| stops_list = [] | |
| raw_list = [] | |
| if isinstance(data, list): | |
| raw_list = data | |
| elif isinstance(data, dict): | |
| raw_list = data.get("APIGetActiveMiddleCitiesListResult", []) | |
| if not raw_list: | |
| raw_list = data.get("data", []) | |
| for i, stop in enumerate(raw_list): | |
| stops_list.append({ | |
| "placeName": stop.get("CityName", "Unknown"), | |
| "scheduleArrTime": "--:--", | |
| "seqNo": stop.get("Position", i+1) | |
| }) | |
| stops_list.sort(key=lambda x: int(x['seqNo'])) | |
| return jsonify({"data": stops_list}) | |
| else: | |
| print(f"KSRTC Stops API Error: {response.status_code}") | |
| return jsonify({"error": "Failed to fetch stops"}), 500 | |
| except Exception as e: | |
| print(f"KSRTC Stops Exception: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| def get_tgsrtc_db(): | |
| conn = sqlite3.connect('tgsrtc.db') | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def resolve_tgsrtc_id(): | |
| req_data = request.get_json() | |
| place_name = req_data.get('address', '').split(',')[0].strip() | |
| if not place_name: | |
| return jsonify({"ids": [], "matches": []}), 400 | |
| conn = get_tgsrtc_db() | |
| cursor = conn.cursor() | |
| patterns = [ | |
| (place_name, 'exact'), | |
| (f"{place_name}%", 'starts_with'), | |
| (f"%{place_name}%", 'contains') | |
| ] | |
| all_stops = [] | |
| for pattern, match_type in patterns: | |
| cursor.execute(""" | |
| SELECT stop_id, stop_name, stop_lat, stop_lon | |
| FROM stops | |
| WHERE stop_name LIKE ? | |
| ORDER BY stop_name | |
| """, (pattern,)) | |
| stops = cursor.fetchall() | |
| if stops: | |
| all_stops = stops | |
| break | |
| conn.close() | |
| if all_stops: | |
| return jsonify({ | |
| "id": all_stops[0]['stop_id'], | |
| "match": all_stops[0]['stop_name'], | |
| "state": "TGSRTC", | |
| "allMatches": [ | |
| { | |
| "id": stop['stop_id'], | |
| "name": stop['stop_name'], | |
| "lat": stop['stop_lat'], | |
| "lon": stop['stop_lon'] | |
| } for stop in all_stops | |
| ] | |
| }) | |
| return jsonify({"id": None, "allMatches": []}) | |
| def find_buses_tgsrtc(): | |
| req_data = request.get_json() | |
| from_id = str(req_data.get('fromId')) | |
| to_id = str(req_data.get('toId')) | |
| if not from_id or not to_id: | |
| return jsonify({"error": "Missing Stop IDs"}), 400 | |
| conn = get_tgsrtc_db() | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_id = ?", (from_id,)) | |
| from_stop = cursor.fetchone() | |
| cursor.execute("SELECT stop_id, stop_name FROM stops WHERE stop_id = ?", (to_id,)) | |
| to_stop = cursor.fetchone() | |
| if not from_stop or not to_stop: | |
| conn.close() | |
| return jsonify({"error": "Invalid stop IDs"}), 400 | |
| print(f"FROM TG ID: {from_id} ({from_stop['stop_name']})") | |
| print(f"TO TG ID: {to_id} ({to_stop['stop_name']})") | |
| def extract_base_name(stop_name): | |
| for delimiter in [' - ', ',', '(']: | |
| if delimiter in stop_name: | |
| stop_name = stop_name.split(delimiter)[0] | |
| return stop_name.strip() | |
| from_base = extract_base_name(from_stop['stop_name']) | |
| to_base = extract_base_name(to_stop['stop_name']) | |
| cursor.execute("SELECT stop_id FROM stops WHERE stop_name LIKE ? OR stop_name LIKE ?", | |
| (f"{from_base}%", f"%{from_base}%")) | |
| from_stop_ids = [str(row['stop_id']) for row in cursor.fetchall()] | |
| cursor.execute("SELECT stop_id FROM stops WHERE stop_name LIKE ? OR stop_name LIKE ?", | |
| (f"{to_base}%", f"%{to_base}%")) | |
| to_stop_ids = [str(row['stop_id']) for row in cursor.fetchall()] | |
| if not from_stop_ids or not to_stop_ids: | |
| conn.close() | |
| return jsonify({"error": "No matching stops found", "data": [], "totalBuses": 0}) | |
| from_placeholders = ','.join('?' * len(from_stop_ids)) | |
| to_placeholders = ','.join('?' * len(to_stop_ids)) | |
| utc_now = datetime.utcnow() | |
| ist_now = utc_now + timedelta(hours=5, minutes=30) | |
| current_time = ist_now.strftime("%H:%M:%S") | |
| query = f''' | |
| SELECT DISTINCT | |
| r.route_short_name, | |
| t.trip_id, | |
| t.trip_headsign, | |
| t.bus_class, | |
| st1.departure_time, | |
| st1.stop_id as from_stop_id, | |
| st2.arrival_time, | |
| st2.stop_id as to_stop_id | |
| FROM stop_times st1 | |
| JOIN stop_times st2 ON st1.trip_id = st2.trip_id | |
| JOIN trips t ON st1.trip_id = t.trip_id | |
| LEFT JOIN routes r ON t.route_id = r.route_id | |
| WHERE st1.stop_id IN ({from_placeholders}) | |
| AND st2.stop_id IN ({to_placeholders}) | |
| AND CAST(st1.stop_sequence AS INTEGER) < CAST(st2.stop_sequence AS INTEGER) | |
| AND st1.departure_time >= ? | |
| ORDER BY st1.departure_time | |
| LIMIT 200 | |
| ''' | |
| cursor.execute(query, from_stop_ids + to_stop_ids + [current_time]) | |
| rows = cursor.fetchall() | |
| results = [] | |
| seen_trips = set() | |
| for row in rows: | |
| trip_key = f"{row['trip_id']}_{row['departure_time']}" | |
| if trip_key in seen_trips: | |
| continue | |
| seen_trips.add(trip_key) | |
| dep_time_obj = datetime.strptime(row['departure_time'], "%H:%M:%S") | |
| dep_time_str = dep_time_obj.strftime("%I:%M %p") | |
| arr_time_obj = datetime.strptime(row['arrival_time'], "%H:%M:%S") | |
| arr_time_str = arr_time_obj.strftime("%I:%M %p") | |
| diff = arr_time_obj - dep_time_obj | |
| if diff.days < 0: | |
| diff = timedelta(days=1) + diff | |
| hours = diff.seconds // 3600 | |
| minutes = (diff.seconds // 60) % 60 | |
| duration = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m" | |
| cursor.execute("SELECT stop_name FROM stops WHERE stop_id = ?", (row['from_stop_id'],)) | |
| actual_from = cursor.fetchone() | |
| cursor.execute("SELECT stop_name FROM stops WHERE stop_id = ?", (row['to_stop_id'],)) | |
| actual_to = cursor.fetchone() | |
| results.append({ | |
| "oprsNo": row['route_short_name'] or "N/A", | |
| "serviceType": row['bus_class'] or "Ordinary", | |
| "serviceStartTime": dep_time_str, | |
| "serviceEndTime": arr_time_str, | |
| "duration": duration, | |
| "via": row['trip_headsign'] or "TGSRTC Route", | |
| "route": f"{actual_from['stop_name']} → {actual_to['stop_name']}", | |
| "fromStopName": actual_from['stop_name'], | |
| "toStopName": actual_to['stop_name'], | |
| "source": "TGSRTC", | |
| "serviceDocId": row['trip_id'], | |
| "journeyDate": datetime.now().strftime("%d-%m-%Y") | |
| }) | |
| conn.close() | |
| print(results) | |
| return jsonify({"data": results, "source": "TGSRTC", "totalBuses": len(results)}) | |
| def get_tgsrtc_stops(): | |
| req_data = request.get_json() | |
| trip_id = str(req_data.get('tripId')) | |
| if not trip_id: | |
| return jsonify({"error": "Missing trip ID"}), 400 | |
| conn = get_tgsrtc_db() | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT | |
| st.stop_sequence, | |
| st.arrival_time, | |
| st.departure_time, | |
| s.stop_name | |
| FROM stop_times st | |
| JOIN stops s ON st.stop_id = s.stop_id | |
| WHERE st.trip_id = ? | |
| ORDER BY CAST(st.stop_sequence AS INTEGER) | |
| """, (trip_id,)) | |
| stops = cursor.fetchall() | |
| conn.close() | |
| if not stops: | |
| return jsonify({"error": "No stops found", "data": []}) | |
| stops_data = [] | |
| for stop in stops: | |
| time_obj = datetime.strptime(stop['arrival_time'], "%H:%M:%S") | |
| time_str = time_obj.strftime("%I:%M %p") | |
| stops_data.append({ | |
| "placeName": stop['stop_name'], | |
| "scheduleArrTime": time_str | |
| }) | |
| return jsonify({"data": stops_data}) | |
| def index(): | |
| return render_template('index.html') | |
| if __name__ =="__main__": | |
| app.run(debug=True) | |